Example #1
0
        /// <summary>
        /// This generates a 6, 7 or 8 digit code from the secret key and seed counter.
        /// </summary>
        /// <param name="key">Secret key</param>
        /// <param name="counter">8byte counter to seed into the key</param>
        ///<param name="length">length of key, most often 6 digits is used.</param>
        ///<returns>Code digits</returns>
        /// <exception cref="ArgumentNullException">If key is null</exception>
        /// <remarks><![CDATA[
        /// Background info (RFC4226):
        /// The following code example describes the extraction of a dynamic
        /// binary code given that hmac_result is a byte array with the HMAC-
        /// SHA-1 result:
        /// 
        ///      int offset   =  hmac_result[19] & 0xf ;
        ///      int bin_code = (hmac_result[offset]  & 0x7f) << 24
        ///         | (hmac_result[offset+1] & 0xff) << 16
        ///         | (hmac_result[offset+2] & 0xff) <<  8
        ///         | (hmac_result[offset+3] & 0xff) ;
        /// 
        /// SHA-1 HMAC Bytes (Example)
        /// 
        /// -------------------------------------------------------------
        /// | Byte Number                                               |
        /// -------------------------------------------------------------
        /// |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|
        /// -------------------------------------------------------------
        /// | Byte Value                                                |
        /// -------------------------------------------------------------
        /// |1f|86|98|69|0e|02|ca|16|61|85|50|ef|7f|19|da|8e|94|5b|55|5a|
        /// -------------------------------***********----------------++|
        ///
        /// * The last byte (byte 19) has the hex value 0x5a.
        /// * The value of the lower 4 bits is 0xa (the offset value).
        /// * The offset value is byte 10 (0xa).
        /// * The value of the 4 bytes starting at byte 10 is 0x50ef7f19,
        ///   which is the dynamic binary code DBC1.
        /// * The MSB of DBC1 is 0x50 so DBC2 = DBC1 = 0x50ef7f19 .
        /// * HOTP = DBC2 modulo 10^6 = 872921.
        ///
        /// We treat the dynamic binary code as a 31-bit, unsigned, big-endian
        /// integer; the first byte is masked with a 0x7f.
        ///
        /// We then take this number modulo 1,000,000 (10^6) to generate the 6-
        /// digit HOTP value 872921 decimal.
        ///]]></remarks>
        public static string Generate(byte[] key, long counter, CodeLength length)
        {
            byte[] hmacResult;
            byte[] byteCounter = BitConverter.GetBytes(counter);

            if (key == null) throw new ArgumentNullException("key", "Key may not be null");

            //If we are on a system that uses Little Endian, like intel ;-)
            //RFC4226 states we need Big Endian.
            if (BitConverter.IsLittleEndian) byteCounter = byteCounter.Reverse().ToArray();

            //do hash
            using (var hmac = new HMACSHA1(key))
            {
                hmacResult = hmac.ComputeHash(byteCounter);
            }

            var offset = hmacResult[19] & 0xf;

            //first byte is masked with a 0x7f as per RFC
            var binCode = (hmacResult[offset] & 0x7f) << 24
                    | (hmacResult[offset + 1] & 0xff) << 16
                    | (hmacResult[offset + 2] & 0xff) << 8
                    | (hmacResult[offset + 3] & 0xff);

            //modulo our binCode with the code length (6, 7 or 8)
            //make sure to pad left with 0's.
            int i = (int) length;
            var code = (binCode%Math.Pow(10, i)).ToString(CultureInfo.InvariantCulture).PadLeft(i, '0');

            return code;
        }
 /// <summary>
 /// Generates a onetime password code
 /// </summary>
 /// <param name="key">shared secret</param>
 /// <param name="offset">interval offset, default is 0</param>
 /// <param name="length">length of code, six, seven or eight. Default is six</param>
 /// <param name="timeStep">timestep eg. secs code should work, default is 30</param>
 /// <returns>code generated</returns>
 public static string Generate(byte[] key, int offset = 0, CodeLength length = CodeLength.Six, int timeStep=30)
 {
     return Generate(key, GetCurrentTimeInterval(timeStep) + offset, length);
 }