C# Class for OATH/HMAC/SHA1 OTP (one time password)
$100-200 USD
Terminado
Publicado hace más de 15 años
$100-200 USD
Pagado a la entrega
I need a C# class with a single static method that can generate the proper one time password given a secret, a counter, and a length. The signature of the class should be something like this...
public class HmacOTP
{
public static String Generate(String secret, int counter)
{
String otp;
// do work
return otp;
}
}
===== Background =====
This is using the one time password (OTP) standard put out by the OATH consortium ([login to view URL]). This OTP technology is usually seen in hardware security tokens ([login to view URL]).
I have a short class in Ruby that does this.
## Deliverables
NOTE: this must be in managed code, all C#.
Also, there is an HMACSHA1 class in .NET that is probably the best starting point.
RFC for this can be found here ([login to view URL]).
===== Ruby equivalent =====
Here is a class in Ruby that does what I need...
=begin rdoc
Copyright (c) 2007, Vonage Holdings
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Vonage Holdings nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Authors: Michael DeCandia <mdecandia@[login to view URL]> and Stephen Becker <sbecker@[login to view URL]>
=== HOTP
The HOTP object can be used to calculate the HOTP value for any secret and count. The number of digits in the HOTP value is determined by an optional parameter. The default HOTP value is 6 digits long.
Creating an HOTP instance
h = [login to view URL]()
h.secret="12345678901234567890" # 20 byte key
h.count=0 # Where to start calculating
h.digits=6 # How many digits to return (length of the HOTP value)
puts([login to view URL]) # => "755224"
h.count=1 # Change the count
puts([login to view URL]) # => "287082"
Using HOTP as a class method
secret = "12345678901234567890"
count = 0
puts(HOTP::hotp(secret,count)) # => "755224"
=end
require 'openssl'
require 'logger'
#This implements HOTP: An HMAC-Based One-Time Password Algorithm as described in RFC 4226 ([login to view URL])
class HOTP
#The secret key in ascii (K)
attr_accessor :secret
#The count used to start the calculation (C)
attr_accessor :count
#The length of the HOTP value (Digit) (Defaults to 6)
attr_accessor :digits
#The default logger to use when reporting an error. (Defaults to STDERR)
@@logger = [login to view URL](STDERR)
#Creates an HOTP object
#* :secret is your secret key
#* :count is where to start your calculation
#* :digits is the length of the HOTP value
#
#Creating an HOTP instance
#
# h = [login to view URL]()
# h.secret="12345678901234567890" # 20 byte key
# h.count=0 # Where to start calculating
# h.digits=6 # How many digits to return (length of the HOTP value)
# puts([login to view URL]) # => "755224"
# h.count=1 # Change the count
# puts([login to view URL]) # => "287082"
#
#
#Using HOTP as a class method
#
# secret = "12345678901234567890"
# count = 0
# puts(HOTP::hotp(secret,count)) # => "755224"
#
def initialize(secret=nil,count=nil,digits=6)
begin
@secret = secret
@count = count
@digits = digits
rescue Exception => e
@@[login to view URL]{"Unable to initialize HOTP properly. Reason: #{e}"}
end
end
#Returns the HOTP value as a string
def update
begin
return calculate_hotp(@secret, @count, @digits)
rescue Exception => e
@@[login to view URL]{"Unable to update. Reason #{e}"}
end
end
alias_method :hotp, :update
#Returns the HOTP value as a string
def calculate_hotp(secret,count,digits=6)
return HOTP::hotp(secret,count,digits)
end
#Set the Logger object. By default this logs to STDERR
def self.logger=(logger)
@@logger = logger
end
#Class method that returns the HOTP value as a string
def [login to view URL](secret,count,digits=6)
begin
# The secret is plain text
# i.e "1234567891234567890"
# The count needs to be in hex so we need to convert it into a padded hex string
# i.e. 10 = "\x00\x00\x00\x00\x00\x00\x00\x0a"
# The digits are the number of digits to use when calculating the HOTP. (defaults to 6)
sha1_hash = OpenSSL::[login to view URL](OpenSSL::Digest::[login to view URL]("SHA1"), secret, string_to_hex_value(count,8))
#the last hex value is the offset
offset = sha1_hash[-1].[login to view URL]
#get the string in the offset range
dbc1 = sha1_hash[(offset*2)...((offset*2)+8)]
#31bits
dbc2 = [login to view URL] & "7fffffff".hex
hotp = dbc2%(10**digits.to_i)
hotp = sprintf("%0#{digits}d", hotp)
return hotp
rescue Exception => e
@@[login to view URL]{"Unable to calculate the HOTP value. Reason: #{e}"}
end
end
private
#Private class method that converts a string to a padded hex value
def self.string_to_hex_value(count,padding=nil,pad_char="0")
begin
return_string = ""
# Change count to an integer then base 16 convert to a string
# i.e. 10.to_s(16) = a
hex_count = count.to_i.to_s(16) #string
# Pad the resulting string with the pad_charater ("0" by default)
# i.e. Takes "a" and makes it "000000a"
# We multiply the padding by two since we evaluate the hex as two characters
# i.e. "a" in hex is "0a"
((padding.to_i*2) - [login to view URL]).times{[login to view URL](0,pad_char)} if padding
hex_count = "0"+hex_count if [login to view URL]%2==1
temp_char = "0"
[login to view URL](/../).each { |hexs|
temp_char[0]= [login to view URL]
return_string << temp_char
}
return return_string
rescue Exception => e
@@[login to view URL]{"Unable to convert string to hex. Reason #{e}"}
end
end
end