commit 00e19bbd7ff3e47bab6e6323240bd99a0ec3ac7b Author: silenteh Date: Tue Aug 4 14:15:51 2015 +0200 Version 1.0 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e4514c0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - 1.4 + +script: + - go test -v ./... \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9227724 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2015 Sec51.com + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6780158 --- /dev/null +++ b/README.md @@ -0,0 +1,129 @@ +#### Current test status + +[![Build Status](https://travis-ci.io/github.com/sec51/twofactor/status.png)](https://travis-ci.io/github.com/sec51/twofactor/status.png) + +## `totp` + +This package implements the RFC 6238 OATH-TOTP algorithm; +See also the [godocs](https://godoc.org/github.com/sec51/twofactor/) +for this package. + +### Installation + +```go get github.com/sec51/twofactor``` + +### Features + +* Built-in support for secure crypto keys generation + +* Built-in back-off time when a user fails to authenticate more than 3 times + +* Bult-in serialization and deserialization to store the one time token struct in a persistence layer + +* Automatic re-synchronization with the client device + +* Built-in generation of a PNG QR Code for adding easily the secret key on the user device + +* Supports 6, 7, 8 digits tokens + +* Supports HMAC-SHA1, HMAC-SHA256, HMAC-SHA512 + + +### Storing Keys + +> **The key crerated is using go crypto random function and it's a cryptographic secret key.** +> It needs to be protected against unauthorized access and they cannot be leaked. +> In addition when shared with the client, the connection should be secured. + +The `totp` struct can be easily serialized using the `ToBytes()` function. +The bytes can then be stored on a persistent layer. Again the secret key needs to be protected. +You can then retrieve the object back with the function: `TOTPFromBytes` + +Again if you trannsfer those bytes via a network connection, this should be a secured one. + +The struct needs to be stored in a persistent layer becase its values, like last token verification time, +max user authentication failures, etc.. needs to be preserved. +The secret key needs to be preserved too, between the user accound and the user device. +The secret key is used to derive tokens. +Once more the secret key needs to be safely stored. + +### Upcoming features + +* Securely store the secret keys in the persistent layer and allow secure transfer on the network + +* Integration with Twilio for sending the token via SMS, in case the user loses its entry in the Google authenticator app. + + +### Example Usages + +#### Case 1: Google Authenticator + +* How to use the library + +1- Import the library + +``` +import github.com/sec51/twofactor +``` + +2- Instanciate the `totp` object via: + +``` + otp, err := twofactor.NewTOTP("info@sec51.com", "Sec51", crypto.SHA1, 8) + if err != nil { + return err + } +``` + +3- Display the PNG QR code to the user and an input text field, so that he can insert the token generated from his device + +``` + qrBytes, err := otp.QR() + if err != nil { + return err + } +``` + +4- Verify the user provided token, coming from the google authenticator app + +``` + err := otp.Validate(USER_PROVIDED_TOKEN) + if err != nil { + return err + } + // if there is an error, then the authentication failed + // if it succeeded, then store this information and do not display the QR code ever again. +``` + +5- All following authentications should display only a input field with no QR code. + + +### References + +* [RFC 6238 - *TOTP: Time-Based One-Time Password Algorithm*](https://tools.ietf.org/rfc/rfc6238.txt) + +* The [Key URI Format](https://code.google.com/p/google-authenticator/wiki/KeyUriFormat) + + +### Author + +`totp` was written by Sec51 . + + +### License + +``` +Copyright (c) 2015 Sec51.com + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +``` diff --git a/conversions.go b/conversions.go new file mode 100644 index 0000000..31cdd3a --- /dev/null +++ b/conversions.go @@ -0,0 +1,51 @@ +package twofactor + +import ( + "math" +) + +// Helper function which rounds the float to the nearest integet +func round(n float64) uint64 { + if n < 0 { + return uint64(math.Ceil(n - 0.5)) + } + return uint64(math.Floor(n + 0.5)) +} + +// helper function which converts a uint64 to a []byte in Big Endian +func bigEndianUint64(n uint64) [8]byte { + data := [8]byte{} + data[0] = byte((n >> 56) & 0xFF) + data[1] = byte((n >> 48) & 0xFF) + data[2] = byte((n >> 40) & 0xFF) + data[3] = byte((n >> 32) & 0xFF) + data[4] = byte((n >> 24) & 0xFF) + data[5] = byte((n >> 16) & 0xFF) + data[6] = byte((n >> 8) & 0xFF) + data[7] = byte(n & 0xFF) + return data +} + +// helper function which converts a big endian []byte to a uint64 +func uint64FromBigEndian(data [8]byte) uint64 { + i := (uint64(data[7]) << 0) | (uint64(data[6]) << 8) | + (uint64(data[5]) << 16) | (uint64(data[4]) << 24) | + (uint64(data[3]) << 32) | (uint64(data[2]) << 40) | + (uint64(data[1]) << 48) | (uint64(data[0]) << 56) + return uint64(i) +} + +func bigEndianInt(n int) [4]byte { + data := [4]byte{} + data[0] = byte((n >> 24) & 0xFF) + data[1] = byte((n >> 16) & 0xFF) + data[2] = byte((n >> 8) & 0xFF) + data[3] = byte(n & 0xFF) + return data +} + +func intFromBigEndian(data [4]byte) int { + i := (int(data[3]) << 0) | (int(data[2]) << 8) | + (int(data[1]) << 16) | (int(data[0]) << 24) + return int(i) +} diff --git a/conversions_test.go b/conversions_test.go new file mode 100644 index 0000000..5d4c25f --- /dev/null +++ b/conversions_test.go @@ -0,0 +1,101 @@ +package twofactor + +import ( + "encoding/binary" + "testing" +) + +func TestRound(t *testing.T) { + + // TODO: test negative numbers, although not used in our case + + input := float64(3.7) + expected := uint64(4) + result := round(input) + if result != expected { + t.Fatalf("Expected %d - got %d\n", expected, result) + } + + input = float64(3.5) + expected = uint64(4) + result = round(input) + if result != expected { + t.Fatalf("Expected %d - got %d\n", expected, result) + } + + input = float64(3.499999999) + expected = uint64(3) + result = round(input) + if result != expected { + t.Fatalf("Expected %d - got %d\n", expected, result) + } + + input = float64(3.0) + expected = uint64(3) + result = round(input) + if result != expected { + t.Fatalf("Expected %d - got %d\n", expected, result) + } + + input = float64(3.9999) + expected = uint64(4) + result = round(input) + if result != expected { + t.Fatalf("Expected %d - got %d\n", expected, result) + } +} + +func TestBigEndianUint64(t *testing.T) { + + // convert ot bytes + input := uint64(2984983220) + inputBytes := bigEndianUint64(input) + + // convert from bytes back + result := uint64FromBigEndian(inputBytes) + if result != input { + t.Fatal("Big endian conversion failed") + } + + goResult := binary.BigEndian.Uint64(inputBytes[:]) + + if goResult != input { + t.Fatal("It's not a big endian representation") + } + + input = uint64(18446744073709551615) + inputBytes = bigEndianUint64(input) + + // convert from bytes back + result = uint64FromBigEndian(inputBytes) + if result != input { + t.Fatal("Big endian conversion failed") + } + + goResult = binary.BigEndian.Uint64(inputBytes[:]) + + if goResult != input { + t.Fatal("It's not a big endian representation") + } + +} + +func TestBigEndianInt(t *testing.T) { + + // convert ot bytes + input := int(2984983220) + inputBytes := bigEndianInt(input) + + // convert from bytes back + result := intFromBigEndian(inputBytes) + if result != input { + t.Fatal("Big endian conversion failed") + } + + goResult := binary.BigEndian.Uint32(inputBytes[:]) + + if int(goResult) != input { + t.Fatal("It's not a big endian representation") + } + +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..c2ef59d --- /dev/null +++ b/doc.go @@ -0,0 +1,25 @@ +/* +The package twofactor implements the RFC 6238 TOTP: Time-Based One-Time Password Algorithm + +The library provides a simple and secure way to generate and verify the OTP tokens +and provides the possibility to display QR codes out of the box + +The library supports HMAC-SHA1, HMAC-SHA256, HMAC-SHA512 +*/ +package twofactor + +/* + Copyright (c) 2015 Sec51 + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ diff --git a/rfc4226.txt b/rfc4226.txt new file mode 100644 index 0000000..b852364 --- /dev/null +++ b/rfc4226.txt @@ -0,0 +1,2075 @@ + + + + + + +Network Working Group D. M'Raihi +Request for Comments: 4226 VeriSign +Category: Informational M. Bellare + UCSD + F. Hoornaert + Vasco + D. Naccache + Gemplus + O. Ranen + Aladdin + December 2005 + + + HOTP: An HMAC-Based One-Time Password Algorithm + +Status of This Memo + + This memo provides information for the Internet community. It does + not specify an Internet standard of any kind. Distribution of this + memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2005). + +Abstract + + This document describes an algorithm to generate one-time password + values, based on Hashed Message Authentication Code (HMAC). A + security analysis of the algorithm is presented, and important + parameters related to the secure deployment of the algorithm are + discussed. The proposed algorithm can be used across a wide range of + network applications ranging from remote Virtual Private Network + (VPN) access, Wi-Fi network logon to transaction-oriented Web + applications. + + This work is a joint effort by the OATH (Open AuTHentication) + membership to specify an algorithm that can be freely distributed to + the technical community. The authors believe that a common and + shared algorithm will facilitate adoption of two-factor + authentication on the Internet by enabling interoperability across + commercial and open-source implementations. + + + + + + + + + +M'Raihi, et al. Informational [Page 1] + +RFC 4226 HOTP Algorithm December 2005 + + +Table of Contents + + 1. Overview ........................................................3 + 2. Introduction ....................................................3 + 3. Requirements Terminology ........................................4 + 4. Algorithm Requirements ..........................................4 + 5. HOTP Algorithm ..................................................5 + 5.1. Notation and Symbols .......................................5 + 5.2. Description ................................................6 + 5.3. Generating an HOTP Value ...................................6 + 5.4. Example of HOTP Computation for Digit = 6 ..................7 + 6. Security Considerations .........................................8 + 7. Security Requirements ...........................................9 + 7.1. Authentication Protocol Requirements .......................9 + 7.2. Validation of HOTP Values .................................10 + 7.3. Throttling at the Server ..................................10 + 7.4. Resynchronization of the Counter ..........................11 + 7.5. Management of Shared Secrets ..............................11 + 8. Composite Shared Secrets .......................................14 + 9. Bi-Directional Authentication ..................................14 + 10. Conclusion ....................................................15 + 11. Acknowledgements ..............................................15 + 12. Contributors ..................................................15 + 13. References ....................................................15 + 13.1. Normative References .....................................15 + 13.2. Informative References ...................................16 + Appendix A - HOTP Algorithm Security: Detailed Analysis ...........17 + A.1. Definitions and Notations .................................17 + A.2. The Idealized Algorithm: HOTP-IDEAL .......................17 + A.3. Model of Security .........................................18 + A.4. Security of the Ideal Authentication Algorithm ............19 + A.4.1. From Bits to Digits ................................19 + A.4.2. Brute Force Attacks ................................21 + A.4.3. Brute force attacks are the best possible attacks ..22 + A.5. Security Analysis of HOTP .................................23 + Appendix B - SHA-1 Attacks ........................................25 + B.1. SHA-1 Status ..............................................25 + B.2. HMAC-SHA-1 Status .........................................26 + B.3. HOTP Status ...............................................26 + Appendix C - HOTP Algorithm: Reference Implementation .............27 + Appendix D - HOTP Algorithm: Test Values ..........................32 + Appendix E - Extensions ...........................................33 + E.1. Number of Digits ..........................................33 + E.2. Alphanumeric Values .......................................33 + E.3. Sequence of HOTP values ...................................34 + E.4. A Counter-Based Resynchronization Method ..................34 + E.5. Data Field ................................................35 + + + + +M'Raihi, et al. Informational [Page 2] + +RFC 4226 HOTP Algorithm December 2005 + + +1. Overview + + The document introduces first the context around an algorithm that + generates one-time password values based on HMAC [BCK1] and, thus, is + named the HMAC-Based One-Time Password (HOTP) algorithm. In Section + 4, the algorithm requirements are listed and in Section 5, the HOTP + algorithm is described. Sections 6 and 7 focus on the algorithm + security. Section 8 proposes some extensions and improvements, and + Section 10 concludes this document. In Appendix A, the interested + reader will find a detailed, full-fledged analysis of the algorithm + security: an idealized version of the algorithm is evaluated, and + then the HOTP algorithm security is analyzed. + +2. Introduction + + Today, deployment of two-factor authentication remains extremely + limited in scope and scale. Despite increasingly higher levels of + threats and attacks, most Internet applications still rely on weak + authentication schemes for policing user access. The lack of + interoperability among hardware and software technology vendors has + been a limiting factor in the adoption of two-factor authentication + technology. In particular, the absence of open specifications has + led to solutions where hardware and software components are tightly + coupled through proprietary technology, resulting in high-cost + solutions, poor adoption, and limited innovation. + + In the last two years, the rapid rise of network threats has exposed + the inadequacies of static passwords as the primary mean of + authentication on the Internet. At the same time, the current + approach that requires an end user to carry an expensive, single- + function device that is only used to authenticate to the network is + clearly not the right answer. For two-factor authentication to + propagate on the Internet, it will have to be embedded in more + flexible devices that can work across a wide range of applications. + + The ability to embed this base technology while ensuring broad + interoperability requires that it be made freely available to the + broad technical community of hardware and software developers. Only + an open-system approach will ensure that basic two-factor + authentication primitives can be built into the next generation of + consumer devices such as USB mass storage devices, IP phones, and + personal digital assistants. + + One-Time Password is certainly one of the simplest and most popular + forms of two-factor authentication for securing network access. For + example, in large enterprises, Virtual Private Network access often + requires the use of One-Time Password tokens for remote user + authentication. One-Time Passwords are often preferred to stronger + + + +M'Raihi, et al. Informational [Page 3] + +RFC 4226 HOTP Algorithm December 2005 + + + forms of authentication such as Public-Key Infrastructure (PKI) or + biometrics because an air-gap device does not require the + installation of any client desktop software on the user machine, + therefore allowing them to roam across multiple machines including + home computers, kiosks, and personal digital assistants. + + This document proposes a simple One-Time Password algorithm that can + be implemented by any hardware manufacturer or software developer to + create interoperable authentication devices and software agents. The + algorithm is event-based so that it can be embedded in high-volume + devices such as Java smart cards, USB dongles, and GSM SIM cards. + The presented algorithm is made freely available to the developer + community under the terms and conditions of the IETF Intellectual + Property Rights [RFC3979]. + + The authors of this document are members of the Open AuTHentication + initiative [OATH]. The initiative was created in 2004 to facilitate + collaboration among strong authentication technology providers. + +3. Requirements Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + +4. Algorithm Requirements + + This section presents the main requirements that drove this algorithm + design. A lot of emphasis was placed on end-consumer usability as + well as the ability for the algorithm to be implemented by low-cost + hardware that may provide minimal user interface capabilities. In + particular, the ability to embed the algorithm into high-volume SIM + and Java cards was a fundamental prerequisite. + + R1 - The algorithm MUST be sequence- or counter-based: one of the + goals is to have the HOTP algorithm embedded in high-volume devices + such as Java smart cards, USB dongles, and GSM SIM cards. + + R2 - The algorithm SHOULD be economical to implement in hardware by + minimizing requirements on battery, number of buttons, computational + horsepower, and size of LCD display. + + R3 - The algorithm MUST work with tokens that do not support any + numeric input, but MAY also be used with more sophisticated devices + such as secure PIN-pads. + + R4 - The value displayed on the token MUST be easily read and entered + by the user: This requires the HOTP value to be of reasonable length. + + + +M'Raihi, et al. Informational [Page 4] + +RFC 4226 HOTP Algorithm December 2005 + + + The HOTP value must be at least a 6-digit value. It is also + desirable that the HOTP value be 'numeric only' so that it can be + easily entered on restricted devices such as phones. + + R5 - There MUST be user-friendly mechanisms available to + resynchronize the counter. Section 7.4 and Appendix E.4 details the + resynchronization mechanism proposed in this document + + R6 - The algorithm MUST use a strong shared secret. The length of + the shared secret MUST be at least 128 bits. This document + RECOMMENDs a shared secret length of 160 bits. + +5. HOTP Algorithm + + In this section, we introduce the notation and describe the HOTP + algorithm basic blocks -- the base function to compute an HMAC-SHA-1 + value and the truncation method to extract an HOTP value. + +5.1. Notation and Symbols + + A string always means a binary string, meaning a sequence of zeros + and ones. + + If s is a string, then |s| denotes its length. + + If n is a number, then |n| denotes its absolute value. + + If s is a string, then s[i] denotes its i-th bit. We start numbering + the bits at 0, so s = s[0]s[1]...s[n-1] where n = |s| is the length + of s. + + Let StToNum (String to Number) denote the function that as input a + string s returns the number whose binary representation is s. (For + example, StToNum(110) = 6.) + + Here is a list of symbols used in this document. + + Symbol Represents + ------------------------------------------------------------------- + C 8-byte counter value, the moving factor. This counter + MUST be synchronized between the HOTP generator (client) + and the HOTP validator (server). + + K shared secret between client and server; each HOTP + generator has a different and unique secret K. + + T throttling parameter: the server will refuse connections + from a user after T unsuccessful authentication attempts. + + + +M'Raihi, et al. Informational [Page 5] + +RFC 4226 HOTP Algorithm December 2005 + + + + s resynchronization parameter: the server will attempt to + verify a received authenticator across s consecutive + counter values. + + Digit number of digits in an HOTP value; system parameter. + +5.2. Description + + The HOTP algorithm is based on an increasing counter value and a + static symmetric key known only to the token and the validation + service. In order to create the HOTP value, we will use the HMAC- + SHA-1 algorithm, as defined in RFC 2104 [BCK2]. + + As the output of the HMAC-SHA-1 calculation is 160 bits, we must + truncate this value to something that can be easily entered by a + user. + + HOTP(K,C) = Truncate(HMAC-SHA-1(K,C)) + + Where: + + - Truncate represents the function that converts an HMAC-SHA-1 + value into an HOTP value as defined in Section 5.3. + + The Key (K), the Counter (C), and Data values are hashed high-order + byte first. + + The HOTP values generated by the HOTP generator are treated as big + endian. + +5.3. Generating an HOTP Value + + We can describe the operations in 3 distinct steps: + + Step 1: Generate an HMAC-SHA-1 value Let HS = HMAC-SHA-1(K,C) // HS + is a 20-byte string + + Step 2: Generate a 4-byte string (Dynamic Truncation) + Let Sbits = DT(HS) // DT, defined below, + // returns a 31-bit string + + Step 3: Compute an HOTP value + Let Snum = StToNum(Sbits) // Convert S to a number in + 0...2^{31}-1 + Return D = Snum mod 10^Digit // D is a number in the range + 0...10^{Digit}-1 + + + + +M'Raihi, et al. Informational [Page 6] + +RFC 4226 HOTP Algorithm December 2005 + + + The Truncate function performs Step 2 and Step 3, i.e., the dynamic + truncation and then the reduction modulo 10^Digit. The purpose of + the dynamic offset truncation technique is to extract a 4-byte + dynamic binary code from a 160-bit (20-byte) HMAC-SHA-1 result. + + DT(String) // String = String[0]...String[19] + Let OffsetBits be the low-order 4 bits of String[19] + Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15 + Let P = String[OffSet]...String[OffSet+3] + Return the Last 31 bits of P + + The reason for masking the most significant bit of P is to avoid + confusion about signed vs. unsigned modulo computations. Different + processors perform these operations differently, and masking out the + signed bit removes all ambiguity. + + Implementations MUST extract a 6-digit code at a minimum and possibly + 7 and 8-digit code. Depending on security requirements, Digit = 7 or + more SHOULD be considered in order to extract a longer HOTP value. + + The following paragraph is an example of using this technique for + Digit = 6, i.e., that a 6-digit HOTP value is calculated from the + HMAC value. + +5.4. Example of HOTP Computation for Digit = 6 + + 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| + -------------------------------***********----------------++| + + + + +M'Raihi, et al. Informational [Page 7] + +RFC 4226 HOTP Algorithm December 2005 + + + * 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. + +6. Security Considerations + + The conclusion of the security analysis detailed in the Appendix is + that, for all practical purposes, the outputs of the Dynamic + Truncation (DT) on distinct counter inputs are uniformly and + independently distributed 31-bit strings. + + The security analysis then details the impact of the conversion from + a string to an integer and the final reduction modulo 10^Digit, where + Digit is the number of digits in an HOTP value. + + The analysis demonstrates that these final steps introduce a + negligible bias, which does not impact the security of the HOTP + algorithm, in the sense that the best possible attack against the + HOTP function is the brute force attack. + + Assuming an adversary is able to observe numerous protocol exchanges + and collect sequences of successful authentication values. This + adversary, trying to build a function F to generate HOTP values based + on his observations, will not have a significant advantage over a + random guess. + + The logical conclusion is simply that the best strategy will once + again be to perform a brute force attack to enumerate and try all the + possible values. + + Considering the security analysis in the Appendix of this document, + without loss of generality, we can approximate closely the security + of the HOTP algorithm by the following formula: + + Sec = sv/10^Digit + + + + + + +M'Raihi, et al. Informational [Page 8] + +RFC 4226 HOTP Algorithm December 2005 + + + Where: + - Sec is the probability of success of the adversary; + - s is the look-ahead synchronization window size; + - v is the number of verification attempts; + - Digit is the number of digits in HOTP values. + + Obviously, we can play with s, T (the Throttling parameter that would + limit the number of attempts by an attacker), and Digit until + achieving a certain level of security, still preserving the system + usability. + +7. Security Requirements + + Any One-Time Password algorithm is only as secure as the application + and the authentication protocols that implement it. Therefore, this + section discusses the critical security requirements that our choice + of algorithm imposes on the authentication protocol and validation + software. + + The parameters T and s discussed in this section have a significant + impact on the security -- further details in Section 6 elaborate on + the relations between these parameters and their impact on the system + security. + + It is also important to remark that the HOTP algorithm is not a + substitute for encryption and does not provide for the privacy of + data transmission. Other mechanisms should be used to defeat attacks + aimed at breaking confidentiality and privacy of transactions. + +7.1. Authentication Protocol Requirements + + We introduce in this section some requirements for a protocol P + implementing HOTP as the authentication method between a prover and a + verifier. + + RP1 - P MUST support two-factor authentication, i.e., the + communication and verification of something you know (secret code + such as a Password, Pass phrase, PIN code, etc.) and something you + have (token). The secret code is known only to the user and usually + entered with the One-Time Password value for authentication purpose + (two-factor authentication). + + RP2 - P SHOULD NOT be vulnerable to brute force attacks. This + implies that a throttling/lockout scheme is RECOMMENDED on the + validation server side. + + RP3 - P SHOULD be implemented over a secure channel in order to + protect users' privacy and avoid replay attacks. + + + +M'Raihi, et al. Informational [Page 9] + +RFC 4226 HOTP Algorithm December 2005 + + +7.2. Validation of HOTP Values + + The HOTP client (hardware or software token) increments its counter + and then calculates the next HOTP value HOTP client. If the value + received by the authentication server matches the value calculated by + the client, then the HOTP value is validated. In this case, the + server increments the counter value by one. + + If the value received by the server does not match the value + calculated by the client, the server initiate the resynch protocol + (look-ahead window) before it requests another pass. + + If the resynch fails, the server asks then for another + authentication pass of the protocol to take place, until the + maximum number of authorized attempts is reached. + + If and when the maximum number of authorized attempts is reached, the + server SHOULD lock out the account and initiate a procedure to inform + the user. + +7.3. Throttling at the Server + + Truncating the HMAC-SHA-1 value to a shorter value makes a brute + force attack possible. Therefore, the authentication server needs to + detect and stop brute force attacks. + + We RECOMMEND setting a throttling parameter T, which defines the + maximum number of possible attempts for One-Time Password validation. + The validation server manages individual counters per HOTP device in + order to take note of any failed attempt. We RECOMMEND T not to be + too large, particularly if the resynchronization method used on the + server is window-based, and the window size is large. T SHOULD be + set as low as possible, while still ensuring that usability is not + significantly impacted. + + Another option would be to implement a delay scheme to avoid a brute + force attack. After each failed attempt A, the authentication server + would wait for an increased T*A number of seconds, e.g., say T = 5, + then after 1 attempt, the server waits for 5 seconds, at the second + failed attempt, it waits for 5*2 = 10 seconds, etc. + + The delay or lockout schemes MUST be across login sessions to prevent + attacks based on multiple parallel guessing techniques. + + + + + + + + +M'Raihi, et al. Informational [Page 10] + +RFC 4226 HOTP Algorithm December 2005 + + +7.4. Resynchronization of the Counter + + Although the server's counter value is only incremented after a + successful HOTP authentication, the counter on the token is + incremented every time a new HOTP is requested by the user. Because + of this, the counter values on the server and on the token might be + out of synchronization. + + We RECOMMEND setting a look-ahead parameter s on the server, which + defines the size of the look-ahead window. In a nutshell, the server + can recalculate the next s HOTP-server values, and check them against + the received HOTP client. + + Synchronization of counters in this scenario simply requires the + server to calculate the next HOTP values and determine if there is a + match. Optionally, the system MAY require the user to send a + sequence of (say, 2, 3) HOTP values for resynchronization purpose, + since forging a sequence of consecutive HOTP values is even more + difficult than guessing a single HOTP value. + + The upper bound set by the parameter s ensures the server does not go + on checking HOTP values forever (causing a denial-of-service attack) + and also restricts the space of possible solutions for an attacker + trying to manufacture HOTP values. s SHOULD be set as low as + possible, while still ensuring that usability is not impacted. + +7.5. Management of Shared Secrets + + The operations dealing with the shared secrets used to generate and + verify OTP values must be performed securely, in order to mitigate + risks of any leakage of sensitive information. We describe in this + section different modes of operations and techniques to perform these + different operations with respect to the state of the art in data + security. + + We can consider two different avenues for generating and storing + (securely) shared secrets in the Validation system: + + * Deterministic Generation: secrets are derived from a master + seed, both at provisioning and verification stages and generated + on-the-fly whenever it is required. + * Random Generation: secrets are generated randomly at + provisioning stage and must be stored immediately and kept + secure during their life cycle. + + + + + + + +M'Raihi, et al. Informational [Page 11] + +RFC 4226 HOTP Algorithm December 2005 + + + Deterministic Generation + ------------------------ + + A possible strategy is to derive the shared secrets from a master + secret. The master secret will be stored at the server only. A + tamper-resistant device MUST be used to store the master key and + derive the shared secrets from the master key and some public + information. The main benefit would be to avoid the exposure of the + shared secrets at any time and also avoid specific requirements on + storage, since the shared secrets could be generated on-demand when + needed at provisioning and validation time. + + We distinguish two different cases: + + - A single master key MK is used to derive the shared secrets; + each HOTP device has a different secret, K_i = SHA-1 (MK,i) + where i stands for a public piece of information that identifies + uniquely the HOTP device such as a serial number, a token ID, + etc. Obviously, this is in the context of an application or + service -- different application or service providers will have + different secrets and settings. + - Several master keys MK_i are used and each HOTP device stores a + set of different derived secrets, {K_i,j = SHA-1(MK_i,j)} where + j stands for a public piece of information identifying the + device. The idea would be to store ONLY the active master key + at the validation server, in the Hardware Security Module (HSM), + and keep in a safe place, using secret sharing methods such as + [Shamir] for instance. In this case, if a master secret MK_i is + compromised, then it is possible to switch to another secret + without replacing all the devices. + + The drawback in the deterministic case is that the exposure of the + master secret would obviously enable an attacker to rebuild any + shared secret based on correct public information. The revocation of + all secrets would be required, or switching to a new set of secrets + in the case of multiple master keys. + + On the other hand, the device used to store the master key(s) and + generate the shared secrets MUST be tamper resistant. Furthermore, + the HSM will not be exposed outside the security perimeter of the + validation system, therefore reducing the risk of leakage. + + + + + + + + + + +M'Raihi, et al. Informational [Page 12] + +RFC 4226 HOTP Algorithm December 2005 + + + Random Generation + ----------------- + + The shared secrets are randomly generated. We RECOMMEND following + the recommendations in [RFC4086] and selecting a good and secure + random source for generating these secrets. A (true) random + generator requires a naturally occurring source of randomness. + Practically, there are two possible avenues to consider for the + generation of the shared secrets: + + * Hardware-based generators: they exploit the randomness that + occurs in physical phenomena. A nice implementation can be based on + oscillators and built in such ways that active attacks are more + difficult to perform. + + * Software-based generators: designing a good software random + generator is not an easy task. A simple, but efficient, + implementation should be based on various sources and apply to the + sampled sequence a one-way function such as SHA-1. + + We RECOMMEND selecting proven products, being hardware or software + generators, for the computation of shared secrets. + + We also RECOMMEND storing the shared secrets securely, and more + specifically encrypting the shared secrets when stored using tamper- + resistant hardware encryption and exposing them only when required: + for example, the shared secret is decrypted when needed to verify an + HOTP value, and re-encrypted immediately to limit exposure in the RAM + for a short period of time. The data store holding the shared + secrets MUST be in a secure area, to avoid as much as possible direct + attack on the validation system and secrets database. + + Particularly, access to the shared secrets should be limited to + programs and processes required by the validation system only. We + will not elaborate on the different security mechanisms to put in + place, but obviously, the protection of shared secrets is of the + uttermost importance. + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 13] + +RFC 4226 HOTP Algorithm December 2005 + + +8. Composite Shared Secrets + + It may be desirable to include additional authentication factors in + the shared secret K. These additional factors can consist of any + data known at the token but not easily obtained by others. Examples + of such data include: + + * PIN or Password obtained as user input at the token + * Phone number + * Any unique identifier programmatically available at the token + + In this scenario, the composite shared secret K is constructed during + the provisioning process from a random seed value combined with one + or more additional authentication factors. The server could either + build on-demand or store composite secrets -- in any case, depending + on implementation choice, the token only stores the seed value. When + the token performs the HOTP calculation, it computes K from the seed + value and the locally derived or input values of the other + authentication factors. + + The use of composite shared secrets can strengthen HOTP-based + authentication systems through the inclusion of additional + authentication factors at the token. To the extent that the token is + a trusted device, this approach has the further benefit of not + requiring exposure of the authentication factors (such as the user + input PIN) to other devices. + +9. Bi-Directional Authentication + + Interestingly enough, the HOTP client could also be used to + authenticate the validation server, claiming that it is a genuine + entity knowing the shared secret. + + Since the HOTP client and the server are synchronized and share the + same secret (or a method to recompute it), a simple 3-pass protocol + could be put in place: + 1- The end user enter the TokenID and a first OTP value OTP1; + 2- The server checks OTP1 and if correct, sends back OTP2; + 3- The end user checks OTP2 using his HOTP device and if correct, + uses the web site. + + Obviously, as indicated previously, all the OTP communications have + to take place over a secure channel, e.g., SSL/TLS, IPsec + connections. + + + + + + + +M'Raihi, et al. Informational [Page 14] + +RFC 4226 HOTP Algorithm December 2005 + + +10. Conclusion + + This document describes HOTP, a HMAC-based One-Time Password + algorithm. It also recommends the preferred implementation and + related modes of operations for deploying the algorithm. + + The document also exhibits elements of security and demonstrates that + the HOTP algorithm is practical and sound, the best possible attack + being a brute force attack that can be prevented by careful + implementation of countermeasures in the validation server. + + Eventually, several enhancements have been proposed, in order to + improve security if needed for specific applications. + +11. Acknowledgements + + The authors would like to thank Siddharth Bajaj, Alex Deacon, Loren + Hart, and Nico Popp for their help during the conception and + redaction of this document. + +12. Contributors + + The authors of this document would like to emphasize the role of + three persons who have made a key contribution to this document: + + - Laszlo Elteto is system architect with SafeNet, Inc. + + - Ernesto Frutos is director of Engineering with Authenex, Inc. + + - Fred McClain is Founder and CTO with Boojum Mobile, Inc. + + Without their advice and valuable inputs, this document would not be + the same. + +13. References + +13.1. Normative References + + [BCK1] M. Bellare, R. Canetti and H. Krawczyk, "Keyed Hash + Functions and Message Authentication", Proceedings of + Crypto'96, LNCS Vol. 1109, pp. 1-15. + + [BCK2] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed- + Hashing for Message Authentication", RFC 2104, February + 1997. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + + +M'Raihi, et al. Informational [Page 15] + +RFC 4226 HOTP Algorithm December 2005 + + + [RFC3979] Bradner, S., "Intellectual Property Rights in IETF + Technology", BCP 79, RFC 3979, March 2005. + + [RFC4086] Eastlake, D., 3rd, Schiller, J., and S. Crocker, + "Randomness Requirements for Security", BCP 106, RFC 4086, + June 2005. + +13.2. Informative References + + [OATH] Initiative for Open AuTHentication + http://www.openauthentication.org + + [PrOo] B. Preneel and P. van Oorschot, "MD-x MAC and building + fast MACs from hash functions", Advances in Cryptology + CRYPTO '95, Lecture Notes in Computer Science Vol. 963, D. + Coppersmith ed., Springer-Verlag, 1995. + + [Crack] Crack in SHA-1 code 'stuns' security gurus + http://www.eetimes.com/showArticle.jhtml? + articleID=60402150 + + [Sha1] Bruce Schneier. SHA-1 broken. February 15, 2005. + http://www.schneier.com/blog/archives/2005/02/ + sha1_broken.html + + [Res] Researchers: Digital encryption standard flawed + http://news.com.com/ + Researchers+Digital+encryption+standard+flawed/ + 2100-1002-5579881.html?part=dht&tag=ntop&tag=nl.e703 + + [Shamir] How to Share a Secret, by Adi Shamir. In Communications + of the ACM, Vol. 22, No. 11, pp. 612-613, November, 1979. + + + + + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 16] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix A - HOTP Algorithm Security: Detailed Analysis + + The security analysis of the HOTP algorithm is summarized in this + section. We first detail the best attack strategies, and then + elaborate on the security under various assumptions and the impact of + the truncation and make some recommendations regarding the number of + digits. + + We focus this analysis on the case where Digit = 6, i.e., an HOTP + function that produces 6-digit values, which is the bare minimum + recommended in this document. + +A.1. Definitions and Notations + + We denote by {0,1}^l the set of all strings of length l. + + Let Z_{n} = {0,.., n - 1}. + + Let IntDiv(a,b) denote the integer division algorithm that takes + input integers a, b where a >= b >= 1 and returns integers (q,r) + + the quotient and remainder, respectively, of the division of a by b. + (Thus, a = bq + r and 0 <= r < b.) + + Let H: {0,1}^k x {0,1}^c --> {0,1}^n be the base function that takes + a k-bit key K and c-bit counter C and returns an n-bit output H(K,C). + (In the case of HOTP, H is HMAC-SHA-1; we use this formal definition + for generalizing our proof of security.) + +A.2. The Idealized Algorithm: HOTP-IDEAL + + We now define an idealized counterpart of the HOTP algorithm. In + this algorithm, the role of H is played by a random function that + forms the key. + + To be more precise, let Maps(c,n) denote the set of all functions + mapping from {0,1}^c to {0,1}^n. The idealized algorithm has key + space Maps(c,n), so that a "key" for such an algorithm is a function + h from {0,1}^c to {0,1}^n. We imagine this key (function) to be + drawn at random. It is not feasible to implement this idealized + algorithm, since the key, being a function from {0,1}^c to {0,1}^n, + is way too large to even store. So why consider it? + + Our security analysis will show that as long as H satisfies a certain + well-accepted assumption, the security of the actual and idealized + algorithms is for all practical purposes the same. The task that + really faces us, then, is to assess the security of the idealized + algorithm. + + + +M'Raihi, et al. Informational [Page 17] + +RFC 4226 HOTP Algorithm December 2005 + + + In analyzing the idealized algorithm, we are concentrating on + assessing the quality of the design of the algorithm itself, + independently of HMAC-SHA-1. This is in fact the important issue. + +A.3. Model of Security + + The model exhibits the type of threats or attacks that are being + considered and enables one to assess the security of HOTP and HOTP- + IDEAL. We denote ALG as either HOTP or HOTP-IDEAL for the purpose of + this security analysis. + + The scenario we are considering is that a user and server share a key + K for ALG. Both maintain a counter C, initially zero, and the user + authenticates itself by sending ALG(K,C) to the server. The latter + accepts if this value is correct. + + In order to protect against accidental increment of the user counter, + the server, upon receiving a value z, will accept as long as z equals + ALG(K,i) for some i in the range C,...,C + s-1, where s is the + resynchronization parameter and C is the server counter. If it + accepts with some value of i, it then increments its counter to i+1. + If it does not accept, it does not change its counter value. + + The model we specify captures what an adversary can do and what it + needs to achieve in order to "win". First, the adversary is assumed + to be able to eavesdrop, meaning, to see the authenticator + transmitted by the user. Second, the adversary wins if it can get + the server to accept an authenticator relative to a counter value for + which the user has never transmitted an authenticator. + + The formal adversary, which we denote by B, starts out knowing which + algorithm ALG is being used, knowing the system design, and knowing + all system parameters. The one and only thing it is not given a + priori is the key K shared between the user and the server. + + The model gives B full control of the scheduling of events. It has + access to an authenticator oracle representing the user. By calling + this oracle, the adversary can ask the user to authenticate itself + and get back the authenticator in return. It can call this oracle as + often as it wants and when it wants, using the authenticators it + accumulates to perhaps "learn" how to make authenticators itself. At + any time, it may also call a verification oracle, supplying the + latter with a candidate authenticator of its choice. It wins if the + server accepts this accumulator. + + Consider the following game involving an adversary B that is + attempting to compromise the security of an authentication algorithm + ALG: K x {0,1}^c --> R. + + + +M'Raihi, et al. Informational [Page 18] + +RFC 4226 HOTP Algorithm December 2005 + + + Initializations - A key K is selected at random from K, a counter C + is initialized to 0, and the Boolean value win is set to false. + + Game execution - Adversary B is provided with the two following + oracles: + + Oracle AuthO() + -------------- + A = ALG(K,C) + C = C + 1 + Return O to B + + Oracle VerO(A) + -------------- + i = C + While (i <= C + s - 1 and Win == FALSE) do + If A == ALG(K,i) then Win = TRUE; C = i + 1 + Else i = i + 1 + Return Win to B + + AuthO() is the authenticator oracle and VerO(A) is the verification + oracle. + + Upon execution, B queries the two oracles at will. Let Adv(B) be the + probability that win gets set to true in the above game. This is the + probability that the adversary successfully impersonates the user. + + Our goal is to assess how large this value can be as a function of + the number v of verification queries made by B, the number a of + authenticator oracle queries made by B, and the running time t of B. + This will tell us how to set the throttle, which effectively upper + bounds v. + +A.4. Security of the Ideal Authentication Algorithm + + This section summarizes the security analysis of HOTP-IDEAL, starting + with the impact of the conversion modulo 10^Digit and then focusing + on the different possible attacks. + +A.4.1. From Bits to Digits + + The dynamic offset truncation of a random n-bit string yields a + random 31-bit string. What happens to the distribution when it is + taken modulo m = 10^Digit, as done in HOTP? + + + + + + + +M'Raihi, et al. Informational [Page 19] + +RFC 4226 HOTP Algorithm December 2005 + + + The following lemma estimates the biases in the outputs in this case. + + Lemma 1 + ------- + Let N >= m >= 1 be integers, and let (q,r) = IntDiv(N,m). For z in + Z_{m} let: + + P_{N,m}(z) = Pr [x mod m = z : x randomly pick in Z_{n}] + + Then for any z in Z_{m} + + P_{N,m}(z) = (q + 1) / N if 0 <= z < r + q / N if r <= z < m + + Proof of Lemma 1 + ---------------- + Let the random variable X be uniformly distributed over Z_{N}. Then: + + P_{N,m}(z) = Pr [X mod m = z] + + = Pr [X < mq] * Pr [X mod m = z| X < mq] + + Pr [mq <= X < N] * Pr [X mod m = z| mq <= X < N] + + = mq/N * 1/m + + (N - mq)/N * 1 / (N - mq) if 0 <= z < N - mq + 0 if N - mq <= z <= m + + = q/N + + r/N * 1 / r if 0 <= z < N - mq + 0 if r <= z <= m + + Simplifying yields the claimed equation. + + Let N = 2^31, d = 6, and m = 10^d. If x is chosen at random from + Z_{N} (meaning, is a random 31-bit string), then reducing it to a 6- + digit number by taking x mod m does not yield a random 6-digit + number. + + Rather, x mod m is distributed as shown in the following table: + + Values Probability that each appears as output + ---------------------------------------------------------------- + 0,1,...,483647 2148/2^31 roughly equals to 1.00024045/10^6 + 483648,...,999999 2147/2^31 roughly equals to 0.99977478/10^6 + + If X is uniformly distributed over Z_{2^31} (meaning, is a random + 31-bit string), then the above shows the probabilities for different + outputs of X mod 10^6. The first set of values appears with + + + +M'Raihi, et al. Informational [Page 20] + +RFC 4226 HOTP Algorithm December 2005 + + + probability slightly greater than 10^-6, the rest with probability + slightly less, meaning that the distribution is slightly non-uniform. + + However, as the table above indicates, the bias is small, and as we + will see later, negligible: the probabilities are very close to + 10^-6. + +A.4.2. Brute Force Attacks + + If the authenticator consisted of d random digits, then a brute force + attack using v verification attempts would succeed with probability + sv/10^Digit. + + However, an adversary can exploit the bias in the outputs of + HOTP-IDEAL, predicted by Lemma 1, to mount a slightly better attack. + + Namely, it makes authentication attempts with authenticators that are + the most likely values, meaning the ones in the range 0,...,r - 1, + where (q,r) = IntDiv(2^31,10^Digit). + + The following specifies an adversary in our model of security that + mounts the attack. It estimates the success probability as a + function of the number of verification queries. + + For simplicity, we assume that the number of verification queries is + at most r. With N = 2^31 and m = 10^6, we have r = 483,648, and the + throttle value is certainly less than this, so this assumption is not + much of a restriction. + + Proposition 1 + ------------- + + Suppose m = 10^Digit < 2^31, and let (q,r) = IntDiv(2^31,m). Assume + s <= m. The brute-force-attack adversary B-bf attacks HOTP using v + <= r verification oracle queries. This adversary makes no + authenticator oracle queries, and succeeds with probability + + Adv(B-bf) = 1 - (1 - v(q+1)/2^31)^s + + which is roughly equal to + + sv * (q+1)/2^31 + + With m = 10^6 we get q = 2,147. In that case, the brute force attack + using v verification attempts succeeds with probability + + Adv(B-bf) roughly = sv * 2148/2^31 = sv * 1.00024045/10^6 + + + + +M'Raihi, et al. Informational [Page 21] + +RFC 4226 HOTP Algorithm December 2005 + + + As this equation shows, the resynchronization parameter s has a + significant impact in that the adversary's success probability is + proportional to s. This means that s cannot be made too large + without compromising security. + +A.4.3. Brute force attacks are the best possible attacks. + + A central question is whether there are attacks any better than the + brute force one. In particular, the brute force attack did not + attempt to collect authenticators sent by the user and try to + cryptanalyze them in an attempt to learn how to better construct + authenticators. Would doing this help? Is there some way to "learn" + how to build authenticators that result in a higher success rate than + given by the brute-force attack? + + The following says the answer to these questions is no. No matter + what strategy the adversary uses, and even if it sees, and tries to + exploit, the authenticators from authentication attempts of the user, + its success probability will not be above that of the brute force + attack -- this is true as long as the number of authentications it + observes is not incredibly large. This is valuable information + regarding the security of the scheme. + + Proposition 2 ------------- Suppose m = 10^Digit < 2^31, and let + (q,r) = IntDiv(2^31,m). Let B be any adversary attacking HOTP-IDEAL + using v verification oracle queries and a <= 2^c - s authenticator + oracle queries. Then + + Adv(B) < = sv * (q+1)/ 2^31 + + Note: This result is conditional on the adversary not seeing more + than 2^c - s authentications performed by the user, which is hardly + restrictive as long as c is large enough. + + With m = 10^6, we get q = 2,147. In that case, Proposition 2 says + that any adversary B attacking HOTP-IDEAL and making v verification + attempts succeeds with probability at most + + Equation 1 + ---------- + sv * 2148/2^31 roughly = sv * 1.00024045/10^6 + + Meaning, B's success rate is not more than that achieved by the brute + force attack. + + + + + + + +M'Raihi, et al. Informational [Page 22] + +RFC 4226 HOTP Algorithm December 2005 + + +A.5. Security Analysis of HOTP + + We have analyzed, in the previous sections, the security of the + idealized counterparts HOTP-IDEAL of the actual authentication + algorithm HOTP. We now show that, under appropriate and well- + believed assumption on H, the security of the actual algorithms is + essentially the same as that of its idealized counterpart. + + The assumption in question is that H is a secure pseudorandom + function, or PRF, meaning that its input-output values are + indistinguishable from those of a random function in practice. + + Consider an adversary A that is given an oracle for a function f: + {0,1}^c --> {0, 1}^n and eventually outputs a bit. We denote Adv(A) + as the prf-advantage of A, which represents how well the adversary + does at distinguishing the case where its oracle is H(K,.) from the + case where its oracle is a random function of {0,1}^c to {0,1}^n. + + One possible attack is based on exhaustive search for the key K. If + A runs for t steps and T denotes the time to perform one computation + of H, its prf-advantage from this attack turns out to be (t/T)2^-k. + Another possible attack is a birthday one [PrOo], whereby A can + attain advantage p^2/2^n in p oracle queries and running time about + pT. + + Our assumption is that these are the best possible attacks. This + translates into the following. + + Assumption 1 + ------------ + + Let T denotes the time to perform one computation of H. Then if A is + any adversary with running time at most t and making at most p oracle + queries, + + Adv(A) <= (t/T)/2^k + p^2/2^n + + In practice, this assumption means that H is very secure as PRF. For + example, given that k = n = 160, an attacker with running time 2^60 + and making 2^40 oracle queries has advantage at most (about) 2^-80. + + Theorem 1 + --------- + + Suppose m = 10^Digit < 2^31, and let (q,r) = IntDiv(2^31,m). Let B + be any adversary attacking HOTP using v verification oracle queries, + + + + + +M'Raihi, et al. Informational [Page 23] + +RFC 4226 HOTP Algorithm December 2005 + + + a <= 2^c - s authenticator oracle queries, and running time t. Let T + denote the time to perform one computation of H. If Assumption 1 is + true, then + + Adv(B) <= sv * (q + 1)/2^31 + (t/T)/2^k + ((sv + a)^2)/2^n + + In practice, the (t/T)2^-k + ((sv + a)^2)2^-n term is much smaller + than the sv(q + 1)/2^n term, so that the above says that for all + practical purposes the success rate of an adversary attacking HOTP is + sv(q + 1)/2^n, just as for HOTP-IDEAL, meaning the HOTP algorithm is + in practice essentially as good as its idealized counterpart. + + In the case m = 10^6 of a 6-digit output, this means that an + adversary making v authentication attempts will have a success rate + that is at most that of Equation 1. + + For example, consider an adversary with running time at most 2^60 + that sees at most 2^40 authentication attempts of the user. Both + these choices are very generous to the adversary, who will typically + not have these resources, but we are saying that even such a powerful + adversary will not have more success than indicated by Equation 1. + + We can safely assume sv <= 2^40 due to the throttling and bounds on + s. So: + + (t/T)/2^k + ((sv + a)^2)/2^n <= 2^60/2^160 + (2^41)^2/2^160 + roughly <= 2^-78 + + which is much smaller than the success probability of Equation 1 and + negligible compared to it. + + + + + + + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 24] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix B - SHA-1 Attacks + + This sections addresses the impact of the recent attacks on SHA-1 on + the security of the HMAC-SHA-1-based HOTP. We begin with some + discussion of the situation of SHA-1 and then discuss the relevance + to HMAC-SHA-1 and HOTP. Cited references are in Section 13. + +B.1. SHA-1 Status + + A collision for a hash function h means a pair x,y of different + inputs such that h(x)=h(y). Since SHA-1 outputs 160 bits, a birthday + attack finds a collision in 2^{80} trials. (A trial means one + computation of the function.) This was thought to be the best + possible until Wang, Yin, and Yu announced on February 15, 2005, that + they had an attack finding collisions in 2^{69} trials. + + Is SHA-1 broken? For most practical purposes, we would say probably + not, since the resources needed to mount the attack are huge. Here + is one way to get a sense of it: we can estimate it is about the same + as the time we would need to factor a 760-bit RSA modulus, and this + is currently considered out of reach. + + Burr of NIST is quoted in [Crack] as saying "Large national + intelligence agencies could do this in a reasonable amount of time + with a few million dollars in computer time". However, the + computation may be out of reach of all but such well-funded agencies. + + One should also ask what impact finding SHA-1 collisions actually has + on security of real applications such as signatures. To exploit a + collision x,y to forge signatures, you need to somehow obtain a + signature of x and then you can forge a signature of y. How damaging + this is depends on the content of y: the y created by the attack may + not be meaningful in the application context. Also, one needs a + chosen-message attack to get the signature of x. This seems possible + in some contexts, but not others. Overall, it is not clear that the + impact on the security of signatures is significant. + + Indeed, one can read in the press that SHA-1 is "broken" [Sha1] and + that encryption and SSL are "broken" [Res]. The media have a + tendency to magnify events: it would hardly be interesting to + announce in the news that a team of cryptanalysts did very + interesting theoretical work in attacking SHA-1. + + Cryptographers are excited too. But mainly because this is an + important theoretical breakthrough. Attacks can only get better with + time: it is therefore important to monitor any progress in hash + functions cryptanalysis and be prepared for any really practical + break with a sound migration plan for the future. + + + +M'Raihi, et al. Informational [Page 25] + +RFC 4226 HOTP Algorithm December 2005 + + +B.2. HMAC-SHA-1 Status + + The new attacks on SHA-1 have no impact on the security of + HMAC-SHA-1. The best attack on the latter remains one needing a + sender to authenticate 2^{80} messages before an adversary can create + a forgery. Why? + + HMAC is not a hash function. It is a message authentication code + (MAC) that uses a hash function internally. A MAC depends on a + secret key, while hash functions don't. What one needs to worry + about with a MAC is forgery, not collisions. HMAC was designed so + that collisions in the hash function (here SHA-1) do not yield + forgeries for HMAC. + + Recall that HMAC-SHA-1(K,x) = SHA-1(K_o,SHA-1(K_i,x)) where the keys + K_o,K_i are derived from K. Suppose the attacker finds a pair x,y + such that SHA-1(K_i,x) = SHA-1(K_i,y). (Call this a hidden-key + collision.) Then if it can obtain the MAC of x (itself a tall + order), it can forge the MAC of y. (These values are the same.) But + finding hidden-key collisions is harder than finding collisions, + because the attacker does not know the hidden key K_i. All it may + have is some outputs of HMAC-SHA-1 with key K. To date, there are no + claims or evidence that the recent attacks on SHA-1 extend to find + hidden-key collisions. + + Historically, the HMAC design has already proven itself in this + regard. MD5 is considered broken in that collisions in this hash + function can be found relatively easily. But there is still no + attack on HMAC-MD5 better than the trivial 2^{64} time birthday one. + (MD5 outputs 128 bits, not 160.) We are seeing this strength of HMAC + coming into play again in the SHA-1 context. + +B.3. HOTP Status + + Since no new weakness has surfaced in HMAC-SHA-1, there is no impact + on HOTP. The best attacks on HOTP remain those described in the + document, namely, to try to guess output values. + + The security proof of HOTP requires that HMAC-SHA-1 behave like a + pseudorandom function. The quality of HMAC-SHA-1 as a pseudorandom + function is not impacted by the new attacks on SHA-1, and so neither + is this proven guarantee. + + + + + + + + + +M'Raihi, et al. Informational [Page 26] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix C - HOTP Algorithm: Reference Implementation + + /* + * OneTimePasswordAlgorithm.java + * OATH Initiative, + * HOTP one-time password algorithm + * + */ + + /* Copyright (C) 2004, OATH. All rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "OATH HOTP Algorithm" in all material + * mentioning or referencing this software or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as + * "derived from OATH HOTP algorithm" + * in all material mentioning or referencing the derived work. + * + * OATH (Open AuTHentication) and its members make no + * representations concerning either the merchantability of this + * software or the suitability of this software for any particular + * purpose. + * + * It is provided "as is" without express or implied warranty + * of any kind and OATH AND ITS MEMBERS EXPRESSaLY DISCLAIMS + * ANY WARRANTY OR LIABILITY OF ANY KIND relating to this software. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + + package org.openauthentication.otp; + + import java.io.IOException; + import java.io.File; + import java.io.DataInputStream; + import java.io.FileInputStream ; + import java.lang.reflect.UndeclaredThrowableException; + + import java.security.GeneralSecurityException; + import java.security.NoSuchAlgorithmException; + import java.security.InvalidKeyException; + + import javax.crypto.Mac; + import javax.crypto.spec.SecretKeySpec; + + + + +M'Raihi, et al. Informational [Page 27] + +RFC 4226 HOTP Algorithm December 2005 + + + /** + * This class contains static methods that are used to calculate the + * One-Time Password (OTP) using + * JCE to provide the HMAC-SHA-1. + * + * @author Loren Hart + * @version 1.0 + */ + public class OneTimePasswordAlgorithm { + private OneTimePasswordAlgorithm() {} + + // These are used to calculate the check-sum digits. + // 0 1 2 3 4 5 6 7 8 9 + private static final int[] doubleDigits = + { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 }; + + /** + * Calculates the checksum using the credit card algorithm. + * This algorithm has the advantage that it detects any single + * mistyped digit and any single transposition of + * adjacent digits. + * + * @param num the number to calculate the checksum for + * @param digits number of significant places in the number + * + * @return the checksum of num + */ + public static int calcChecksum(long num, int digits) { + boolean doubleDigit = true; + int total = 0; + while (0 < digits--) { + int digit = (int) (num % 10); + num /= 10; + if (doubleDigit) { + digit = doubleDigits[digit]; + } + total += digit; + doubleDigit = !doubleDigit; + } + int result = total % 10; + if (result > 0) { + result = 10 - result; + } + return result; + } + + /** + * This method uses the JCE to provide the HMAC-SHA-1 + + + +M'Raihi, et al. Informational [Page 28] + +RFC 4226 HOTP Algorithm December 2005 + + + * algorithm. + * HMAC computes a Hashed Message Authentication Code and + * in this case SHA1 is the hash algorithm used. + * + * @param keyBytes the bytes to use for the HMAC-SHA-1 key + * @param text the message or text to be authenticated. + * + * @throws NoSuchAlgorithmException if no provider makes + * either HmacSHA1 or HMAC-SHA-1 + * digest algorithms available. + * @throws InvalidKeyException + * The secret provided was not a valid HMAC-SHA-1 key. + * + */ + + public static byte[] hmac_sha1(byte[] keyBytes, byte[] text) + throws NoSuchAlgorithmException, InvalidKeyException + { + // try { + Mac hmacSha1; + try { + hmacSha1 = Mac.getInstance("HmacSHA1"); + } catch (NoSuchAlgorithmException nsae) { + hmacSha1 = Mac.getInstance("HMAC-SHA-1"); + } + SecretKeySpec macKey = + new SecretKeySpec(keyBytes, "RAW"); + hmacSha1.init(macKey); + return hmacSha1.doFinal(text); + // } catch (GeneralSecurityException gse) { + // throw new UndeclaredThrowableException(gse); + // } + } + + private static final int[] DIGITS_POWER + // 0 1 2 3 4 5 6 7 8 + = {1,10,100,1000,10000,100000,1000000,10000000,100000000}; + + /** + * This method generates an OTP value for the given + * set of parameters. + * + * @param secret the shared secret + * @param movingFactor the counter, time, or other value that + * changes on a per use basis. + * @param codeDigits the number of digits in the OTP, not + * including the checksum, if any. + * @param addChecksum a flag that indicates if a checksum digit + + + +M'Raihi, et al. Informational [Page 29] + +RFC 4226 HOTP Algorithm December 2005 + + + * should be appended to the OTP. + * @param truncationOffset the offset into the MAC result to + * begin truncation. If this value is out of + * the range of 0 ... 15, then dynamic + * truncation will be used. + * Dynamic truncation is when the last 4 + * bits of the last byte of the MAC are + * used to determine the start offset. + * @throws NoSuchAlgorithmException if no provider makes + * either HmacSHA1 or HMAC-SHA-1 + * digest algorithms available. + * @throws InvalidKeyException + * The secret provided was not + * a valid HMAC-SHA-1 key. + * + * @return A numeric String in base 10 that includes + * {@link codeDigits} digits plus the optional checksum + * digit if requested. + */ + static public String generateOTP(byte[] secret, + long movingFactor, + int codeDigits, + boolean addChecksum, + int truncationOffset) + throws NoSuchAlgorithmException, InvalidKeyException + { + // put movingFactor value into text byte array + String result = null; + int digits = addChecksum ? (codeDigits + 1) : codeDigits; + byte[] text = new byte[8]; + for (int i = text.length - 1; i >= 0; i--) { + text[i] = (byte) (movingFactor & 0xff); + movingFactor >>= 8; + } + + // compute hmac hash + byte[] hash = hmac_sha1(secret, text); + + // put selected bytes into result int + int offset = hash[hash.length - 1] & 0xf; + if ( (0<=truncationOffset) && + (truncationOffset<(hash.length-4)) ) { + offset = truncationOffset; + } + int binary = + ((hash[offset] & 0x7f) << 24) + | ((hash[offset + 1] & 0xff) << 16) + | ((hash[offset + 2] & 0xff) << 8) + + + +M'Raihi, et al. Informational [Page 30] + +RFC 4226 HOTP Algorithm December 2005 + + + | (hash[offset + 3] & 0xff); + + int otp = binary % DIGITS_POWER[codeDigits]; + if (addChecksum) { + otp = (otp * 10) + calcChecksum(otp, codeDigits); + } + result = Integer.toString(otp); + while (result.length() < digits) { + result = "0" + result; + } + return result; + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 31] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix D - HOTP Algorithm: Test Values + + The following test data uses the ASCII string + "12345678901234567890" for the secret: + + Secret = 0x3132333435363738393031323334353637383930 + + Table 1 details for each count, the intermediate HMAC value. + + Count Hexadecimal HMAC-SHA-1(secret, count) + 0 cc93cf18508d94934c64b65d8ba7667fb7cde4b0 + 1 75a48a19d4cbe100644e8ac1397eea747a2d33ab + 2 0bacb7fa082fef30782211938bc1c5e70416ff44 + 3 66c28227d03a2d5529262ff016a1e6ef76557ece + 4 a904c900a64b35909874b33e61c5938a8e15ed1c + 5 a37e783d7b7233c083d4f62926c7a25f238d0316 + 6 bc9cd28561042c83f219324d3c607256c03272ae + 7 a4fb960c0bc06e1eabb804e5b397cdc4b45596fa + 8 1b3c89f65e6c9e883012052823443f048b4332db + 9 1637409809a679dc698207310c8c7fc07290d9e5 + + Table 2 details for each count the truncated values (both in + hexadecimal and decimal) and then the HOTP value. + + Truncated + Count Hexadecimal Decimal HOTP + 0 4c93cf18 1284755224 755224 + 1 41397eea 1094287082 287082 + 2 82fef30 137359152 359152 + 3 66ef7655 1726969429 969429 + 4 61c5938a 1640338314 338314 + 5 33c083d4 868254676 254676 + 6 7256c032 1918287922 287922 + 7 4e5b397 82162583 162583 + 8 2823443f 673399871 399871 + 9 2679dc69 645520489 520489 + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 32] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix E - Extensions + + + We introduce in this section several enhancements to the HOTP + algorithm. These are not recommended extensions or part of the + standard algorithm, but merely variations that could be used for + customized implementations. + +E.1. Number of Digits + + A simple enhancement in terms of security would be to extract more + digits from the HMAC-SHA-1 value. + + For instance, calculating the HOTP value modulo 10^8 to build an 8- + digit HOTP value would reduce the probability of success of the + adversary from sv/10^6 to sv/10^8. + + This could give the opportunity to improve usability, e.g., by + increasing T and/or s, while still achieving a better security + overall. For instance, s = 10 and 10v/10^8 = v/10^7 < v/10^6 which + is the theoretical optimum for 6-digit code when s = 1. + +E.2. Alphanumeric Values + + Another option is to use A-Z and 0-9 values; or rather a subset of 32 + symbols taken from the alphanumerical alphabet in order to avoid any + confusion between characters: 0, O, and Q as well as l, 1, and I are + very similar, and can look the same on a small display. + + The immediate consequence is that the security is now in the order of + sv/32^6 for a 6-digit HOTP value and sv/32^8 for an 8-digit HOTP + value. + + 32^6 > 10^9 so the security of a 6-alphanumeric HOTP code is slightly + better than a 9-digit HOTP value, which is the maximum length of an + HOTP code supported by the proposed algorithm. + + 32^8 > 10^12 so the security of an 8-alphanumeric HOTP code is + significantly better than a 9-digit HOTP value. + + Depending on the application and token/interface used for displaying + and entering the HOTP value, the choice of alphanumeric values could + be a simple and efficient way to improve security at a reduced cost + and impact on users. + + + + + + + +M'Raihi, et al. Informational [Page 33] + +RFC 4226 HOTP Algorithm December 2005 + + +E.3. Sequence of HOTP Values + + As we suggested for the resynchronization to enter a short sequence + (say, 2 or 3) of HOTP values, we could generalize the concept to the + protocol, and add a parameter L that would define the length of the + HOTP sequence to enter. + + Per default, the value L SHOULD be set to 1, but if security needs to + be increased, users might be asked (possibly for a short period of + time, or a specific operation) to enter L HOTP values. + + This is another way, without increasing the HOTP length or using + alphanumeric values to tighten security. + + Note: The system MAY also be programmed to request synchronization on + a regular basis (e.g., every night, twice a week, etc.) and to + achieve this purpose, ask for a sequence of L HOTP values. + +E.4. A Counter-Based Resynchronization Method + + In this case, we assume that the client can access and send not only + the HOTP value but also other information, more specifically, the + counter value. + + A more efficient and secure method for resynchronization is possible + in this case. The client application will not send the HOTP-client + value only, but the HOTP-client and the related C-client counter + value, the HOTP value acting as a message authentication code of the + counter. + + Resynchronization Counter-based Protocol (RCP) + ---------------------------------------------- + + The server accepts if the following are all true, where C-server is + its own current counter value: + + 1) C-client >= C-server + 2) C-client - C-server <= s + 3) Check that HOTP client is valid HOTP(K,C-Client) + 4) If true, the server sets C to C-client + 1 and client is + authenticated + + In this case, there is no need for managing a look-ahead window + anymore. The probability of success of the adversary is only v/10^6 + or roughly v in one million. A side benefit is obviously to be able + to increase s "infinitely" and therefore improve the system usability + without impacting the security. + + + + +M'Raihi, et al. Informational [Page 34] + +RFC 4226 HOTP Algorithm December 2005 + + + This resynchronization protocol SHOULD be used whenever the related + impact on the client and server applications is deemed acceptable. + +E.5. Data Field + + Another interesting option is the introduction of a Data field, which + would be used for generating the One-Time Password values: HOTP (K, + C, [Data]) where Data is an optional field that can be the + concatenation of various pieces of identity-related information, + e.g., Data = Address | PIN. + + We could also use a Timer, either as the only moving factor or in + combination with the Counter -- in this case, e.g., Data = Timer, + where Timer could be the UNIX-time (GMT seconds since 1/1/1970) + divided by some factor (8, 16, 32, etc.) in order to give a specific + time step. The time window for the One-Time Password is then equal + to the time step multiplied by the resynchronization parameter as + defined before. For example, if we take 64 seconds as the time step + and 7 for the resynchronization parameter, we obtain an acceptance + window of +/- 3 minutes. + + Using a Data field opens for more flexibility in the algorithm + implementation, provided that the Data field is clearly specified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 35] + +RFC 4226 HOTP Algorithm December 2005 + + +Authors' Addresses + + David M'Raihi (primary contact for sending comments and questions) + VeriSign, Inc. + 685 E. Middlefield Road + Mountain View, CA 94043 USA + + Phone: 1-650-426-3832 + EMail: dmraihi@verisign.com + + + Mihir Bellare + Dept of Computer Science and Engineering, Mail Code 0114 + University of California at San Diego + 9500 Gilman Drive + La Jolla, CA 92093, USA + + EMail: mihir@cs.ucsd.edu + + + Frank Hoornaert + VASCO Data Security, Inc. + Koningin Astridlaan 164 + 1780 Wemmel, Belgium + + EMail: frh@vasco.com + + + David Naccache + Gemplus Innovation + 34 rue Guynemer, 92447, + Issy les Moulineaux, France + and + Information Security Group, + Royal Holloway, + University of London, Egham, + Surrey TW20 0EX, UK + + EMail: david.naccache@gemplus.com, david.naccache@rhul.ac.uk + + + Ohad Ranen + Aladdin Knowledge Systems Ltd. + 15 Beit Oved Street + Tel Aviv, Israel 61110 + + EMail: Ohad.Ranen@ealaddin.com + + + + +M'Raihi, et al. Informational [Page 36] + +RFC 4226 HOTP Algorithm December 2005 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2005). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET + ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE + INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at ietf- + ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + +M'Raihi, et al. Informational [Page 37] + diff --git a/rfc6238.txt b/rfc6238.txt new file mode 100644 index 0000000..8c964f4 --- /dev/null +++ b/rfc6238.txt @@ -0,0 +1,899 @@ + + + + + + +Internet Engineering Task Force (IETF) D. M'Raihi +Request for Comments: 6238 Verisign, Inc. +Category: Informational S. Machani +ISSN: 2070-1721 Diversinet Corp. + M. Pei + Symantec + J. Rydell + Portwise, Inc. + May 2011 + + + TOTP: Time-Based One-Time Password Algorithm + +Abstract + + This document describes an extension of the One-Time Password (OTP) + algorithm, namely the HMAC-based One-Time Password (HOTP) algorithm, + as defined in RFC 4226, to support the time-based moving factor. The + HOTP algorithm specifies an event-based OTP algorithm, where the + moving factor is an event counter. The present work bases the moving + factor on a time value. A time-based variant of the OTP algorithm + provides short-lived OTP values, which are desirable for enhanced + security. + + The proposed algorithm can be used across a wide range of network + applications, from remote Virtual Private Network (VPN) access and + Wi-Fi network logon to transaction-oriented Web applications. The + authors believe that a common and shared algorithm will facilitate + adoption of two-factor authentication on the Internet by enabling + interoperability across commercial and open-source implementations. + +Status of This Memo + + This document is not an Internet Standards Track specification; it is + published for informational purposes. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by the + Internet Engineering Steering Group (IESG). Not all documents + approved by the IESG are a candidate for any level of Internet + Standard; see Section 2 of RFC 5741. + + Information about the current status of this document, any errata, + and how to provide feedback on it may be obtained at + http://www.rfc-editor.org/info/rfc6238. + + + + + +M'Raihi, et al. Informational [Page 1] + +RFC 6238 HOTPTimeBased May 2011 + + +Copyright Notice + + Copyright (c) 2011 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + +Table of Contents + + 1. Introduction ....................................................2 + 1.1. Scope ......................................................2 + 1.2. Background .................................................3 + 2. Notation and Terminology ........................................3 + 3. Algorithm Requirements ..........................................3 + 4. TOTP Algorithm ..................................................4 + 4.1. Notations ..................................................4 + 4.2. Description ................................................4 + 5. Security Considerations .........................................5 + 5.1. General ....................................................5 + 5.2. Validation and Time-Step Size ..............................6 + 6. Resynchronization ...............................................7 + 7. Acknowledgements ................................................7 + 8. References ......................................................8 + 8.1. Normative References .......................................8 + 8.2. Informative References .....................................8 + Appendix A. TOTP Algorithm: Reference Implementation ...............9 + Appendix B. Test Vectors ..........................................14 + +1. Introduction + +1.1. Scope + + This document describes an extension of the One-Time Password (OTP) + algorithm, namely the HMAC-based One-Time Password (HOTP) algorithm, + as defined in [RFC4226], to support the time-based moving factor. + + + + + + + + +M'Raihi, et al. Informational [Page 2] + +RFC 6238 HOTPTimeBased May 2011 + + +1.2. Background + + As defined in [RFC4226], the HOTP algorithm is based on the + HMAC-SHA-1 algorithm (as specified in [RFC2104]) and applied to an + increasing counter value representing the message in the HMAC + computation. + + Basically, the output of the HMAC-SHA-1 calculation is truncated to + obtain user-friendly values: + + HOTP(K,C) = Truncate(HMAC-SHA-1(K,C)) + + where Truncate represents the function that can convert an HMAC-SHA-1 + value into an HOTP value. K and C represent the shared secret and + counter value; see [RFC4226] for detailed definitions. + + TOTP is the time-based variant of this algorithm, where a value T, + derived from a time reference and a time step, replaces the counter C + in the HOTP computation. + + TOTP implementations MAY use HMAC-SHA-256 or HMAC-SHA-512 functions, + based on SHA-256 or SHA-512 [SHA2] hash functions, instead of the + HMAC-SHA-1 function that has been specified for the HOTP computation + in [RFC4226]. + +2. Notation and Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + +3. Algorithm Requirements + + This section summarizes the requirements taken into account for + designing the TOTP algorithm. + + R1: The prover (e.g., token, soft token) and verifier (authentication + or validation server) MUST know or be able to derive the current + Unix time (i.e., the number of seconds elapsed since midnight UTC + of January 1, 1970) for OTP generation. See [UT] for a more + detailed definition of the commonly known "Unix time". The + precision of the time used by the prover affects how often the + clock synchronization should be done; see Section 6. + + R2: The prover and verifier MUST either share the same secret or the + knowledge of a secret transformation to generate a shared secret. + + R3: The algorithm MUST use HOTP [RFC4226] as a key building block. + + + +M'Raihi, et al. Informational [Page 3] + +RFC 6238 HOTPTimeBased May 2011 + + + R4: The prover and verifier MUST use the same time-step value X. + + R5: There MUST be a unique secret (key) for each prover. + + R6: The keys SHOULD be randomly generated or derived using key + derivation algorithms. + + R7: The keys MAY be stored in a tamper-resistant device and SHOULD be + protected against unauthorized access and usage. + +4. TOTP Algorithm + + This variant of the HOTP algorithm specifies the calculation of a + one-time password value, based on a representation of the counter as + a time factor. + +4.1. Notations + + o X represents the time step in seconds (default value X = + 30 seconds) and is a system parameter. + + o T0 is the Unix time to start counting time steps (default value is + 0, i.e., the Unix epoch) and is also a system parameter. + +4.2. Description + + Basically, we define TOTP as TOTP = HOTP(K, T), where T is an integer + and represents the number of time steps between the initial counter + time T0 and the current Unix time. + + More specifically, T = (Current Unix time - T0) / X, where the + default floor function is used in the computation. + + For example, with T0 = 0 and Time Step X = 30, T = 1 if the current + Unix time is 59 seconds, and T = 2 if the current Unix time is + 60 seconds. + + The implementation of this algorithm MUST support a time value T + larger than a 32-bit integer when it is beyond the year 2038. The + value of the system parameters X and T0 are pre-established during + the provisioning process and communicated between a prover and + verifier as part of the provisioning step. The provisioning flow is + out of scope of this document; refer to [RFC6030] for such + provisioning container specifications. + + + + + + + +M'Raihi, et al. Informational [Page 4] + +RFC 6238 HOTPTimeBased May 2011 + + +5. Security Considerations + +5.1. General + + The security and strength of this algorithm depend on the properties + of the underlying building block HOTP, which is a construction based + on HMAC [RFC2104] using SHA-1 as the hash function. + + The conclusion of the security analysis detailed in [RFC4226] is + that, for all practical purposes, the outputs of the dynamic + truncation on distinct inputs are uniformly and independently + distributed strings. + + The analysis demonstrates that the best possible attack against the + HOTP function is the brute force attack. + + As indicated in the algorithm requirement section, keys SHOULD be + chosen at random or using a cryptographically strong pseudorandom + generator properly seeded with a random value. + + Keys SHOULD be of the length of the HMAC output to facilitate + interoperability. + + We RECOMMEND following the recommendations in [RFC4086] for all + pseudorandom and random number generations. The pseudorandom numbers + used for generating the keys SHOULD successfully pass the randomness + test specified in [CN], or a similar well-recognized test. + + All the communications SHOULD take place over a secure channel, e.g., + Secure Socket Layer/Transport Layer Security (SSL/TLS) [RFC5246] or + IPsec connections [RFC4301]. + + We also RECOMMEND storing the keys securely in the validation system, + and, more specifically, encrypting them using tamper-resistant + hardware encryption and exposing them only when required: for + example, the key is decrypted when needed to verify an OTP value, and + re-encrypted immediately to limit exposure in the RAM to a short + period of time. + + The key store MUST be in a secure area, to avoid, as much as + possible, direct attack on the validation system and secrets + database. Particularly, access to the key material should be limited + to programs and processes required by the validation system only. + + + + + + + + +M'Raihi, et al. Informational [Page 5] + +RFC 6238 HOTPTimeBased May 2011 + + +5.2. Validation and Time-Step Size + + An OTP generated within the same time step will be the same. When an + OTP is received at a validation system, it doesn't know a client's + exact timestamp when an OTP was generated. The validation system may + typically use the timestamp when an OTP is received for OTP + comparison. Due to network latency, the gap (as measured by T, that + is, the number of time steps since T0) between the time that the OTP + was generated and the time that the OTP arrives at the receiving + system may be large. The receiving time at the validation system and + the actual OTP generation may not fall within the same time-step + window that produced the same OTP. When an OTP is generated at the + end of a time-step window, the receiving time most likely falls into + the next time-step window. A validation system SHOULD typically set + a policy for an acceptable OTP transmission delay window for + validation. The validation system should compare OTPs not only with + the receiving timestamp but also the past timestamps that are within + the transmission delay. A larger acceptable delay window would + expose a larger window for attacks. We RECOMMEND that at most one + time step is allowed as the network delay. + + The time-step size has an impact on both security and usability. A + larger time-step size means a larger validity window for an OTP to be + accepted by a validation system. There are implications for using a + larger time-step size, as follows: + + First, a larger time-step size exposes a larger window to attack. + When an OTP is generated and exposed to a third party before it is + consumed, the third party can consume the OTP within the time-step + window. + + We RECOMMEND a default time-step size of 30 seconds. This default + value of 30 seconds is selected as a balance between security and + usability. + + Second, the next different OTP must be generated in the next time- + step window. A user must wait until the clock moves to the next + time-step window from the last submission. The waiting time may not + be exactly the length of the time step, depending on when the last + OTP was generated. For example, if the last OTP was generated at the + halfway point in a time-step window, the waiting time for the next + OTP is half the length of the time step. In general, a larger time- + step window means a longer waiting time for a user to get the next + valid OTP after the last successful OTP validation. A too-large + window (for example, 10 minutes) most probably won't be suitable for + typical Internet login use cases; a user may not be able to get the + next OTP within 10 minutes and therefore will have to re-login to the + same site in 10 minutes. + + + +M'Raihi, et al. Informational [Page 6] + +RFC 6238 HOTPTimeBased May 2011 + + + Note that a prover may send the same OTP inside a given time-step + window multiple times to a verifier. The verifier MUST NOT accept + the second attempt of the OTP after the successful validation has + been issued for the first OTP, which ensures one-time only use of an + OTP. + +6. Resynchronization + + Because of possible clock drifts between a client and a validation + server, we RECOMMEND that the validator be set with a specific limit + to the number of time steps a prover can be "out of synch" before + being rejected. + + This limit can be set both forward and backward from the calculated + time step on receipt of the OTP value. If the time step is + 30 seconds as recommended, and the validator is set to only accept + two time steps backward, then the maximum elapsed time drift would be + around 89 seconds, i.e., 29 seconds in the calculated time step and + 60 seconds for two backward time steps. + + This would mean the validator could perform a validation against the + current time and then two further validations for each backward step + (for a total of 3 validations). Upon successful validation, the + validation server can record the detected clock drift for the token + in terms of the number of time steps. When a new OTP is received + after this step, the validator can validate the OTP with the current + timestamp adjusted with the recorded number of time-step clock drifts + for the token. + + Also, it is important to note that the longer a prover has not sent + an OTP to a validation system, the longer (potentially) the + accumulated clock drift between the prover and the verifier. In such + cases, the automatic resynchronization described above may not work + if the drift exceeds the allowed threshold. Additional + authentication measures should be used to safely authenticate the + prover and explicitly resynchronize the clock drift between the + prover and the validator. + +7. Acknowledgements + + The authors of this document would like to thank the following people + for their contributions and support to make this a better + specification: Hannes Tschofenig, Jonathan Tuliani, David Dix, + Siddharth Bajaj, Stu Veath, Shuh Chang, Oanh Hoang, John Huang, and + Siddhartha Mohapatra. + + + + + + +M'Raihi, et al. Informational [Page 7] + +RFC 6238 HOTPTimeBased May 2011 + + +8. References + +8.1. Normative References + + [RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed- + Hashing for Message Authentication", RFC 2104, + February 1997. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC4086] Eastlake 3rd, D., Schiller, J., and S. Crocker, + "Randomness Recommendations for Security", BCP 106, + RFC 4086, June 2005. + + [RFC4226] M'Raihi, D., Bellare, M., Hoornaert, F., Naccache, D., and + O. Ranen, "HOTP: An HMAC-Based One-Time Password + Algorithm", RFC 4226, December 2005. + + [SHA2] NIST, "FIPS PUB 180-3: Secure Hash Standard (SHS)", + October 2008, . + +8.2. Informative References + + [CN] Coron, J. and D. Naccache, "An Accurate Evaluation of + Maurer's Universal Test", LNCS 1556, February 1999, + . + + [RFC4301] Kent, S. and K. Seo, "Security Architecture for the + Internet Protocol", RFC 4301, December 2005. + + [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security + (TLS) Protocol Version 1.2", RFC 5246, August 2008. + + [RFC6030] Hoyer, P., Pei, M., and S. Machani, "Portable Symmetric + Key Container (PSKC)", RFC 6030, October 2010. + + [UT] Wikipedia, "Unix time", February 2011, + . + + + + + + + + + + +M'Raihi, et al. Informational [Page 8] + +RFC 6238 HOTPTimeBased May 2011 + + +Appendix A. TOTP Algorithm: Reference Implementation + + + + /** + Copyright (c) 2011 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in Section + 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + */ + + import java.lang.reflect.UndeclaredThrowableException; + import java.security.GeneralSecurityException; + import java.text.DateFormat; + import java.text.SimpleDateFormat; + import java.util.Date; + import javax.crypto.Mac; + import javax.crypto.spec.SecretKeySpec; + import java.math.BigInteger; + import java.util.TimeZone; + + + /** + * This is an example implementation of the OATH + * TOTP algorithm. + * Visit www.openauthentication.org for more information. + * + * @author Johan Rydell, PortWise, Inc. + */ + + public class TOTP { + + private TOTP() {} + + /** + * This method uses the JCE to provide the crypto algorithm. + * HMAC computes a Hashed Message Authentication Code with the + * crypto hash algorithm as a parameter. + * + * @param crypto: the crypto algorithm (HmacSHA1, HmacSHA256, + * HmacSHA512) + * @param keyBytes: the bytes to use for the HMAC key + * @param text: the message or text to be authenticated + */ + + + +M'Raihi, et al. Informational [Page 9] + +RFC 6238 HOTPTimeBased May 2011 + + + private static byte[] hmac_sha(String crypto, byte[] keyBytes, + byte[] text){ + try { + Mac hmac; + hmac = Mac.getInstance(crypto); + SecretKeySpec macKey = + new SecretKeySpec(keyBytes, "RAW"); + hmac.init(macKey); + return hmac.doFinal(text); + } catch (GeneralSecurityException gse) { + throw new UndeclaredThrowableException(gse); + } + } + + + /** + * This method converts a HEX string to Byte[] + * + * @param hex: the HEX string + * + * @return: a byte array + */ + + private static byte[] hexStr2Bytes(String hex){ + // Adding one byte to get the right conversion + // Values starting with "0" can be converted + byte[] bArray = new BigInteger("10" + hex,16).toByteArray(); + + // Copy all the REAL bytes, not the "first" + byte[] ret = new byte[bArray.length - 1]; + for (int i = 0; i < ret.length; i++) + ret[i] = bArray[i+1]; + return ret; + } + + private static final int[] DIGITS_POWER + // 0 1 2 3 4 5 6 7 8 + = {1,10,100,1000,10000,100000,1000000,10000000,100000000 }; + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 10] + +RFC 6238 HOTPTimeBased May 2011 + + + /** + * This method generates a TOTP value for the given + * set of parameters. + * + * @param key: the shared secret, HEX encoded + * @param time: a value that reflects a time + * @param returnDigits: number of digits to return + * + * @return: a numeric String in base 10 that includes + * {@link truncationDigits} digits + */ + + public static String generateTOTP(String key, + String time, + String returnDigits){ + return generateTOTP(key, time, returnDigits, "HmacSHA1"); + } + + + /** + * This method generates a TOTP value for the given + * set of parameters. + * + * @param key: the shared secret, HEX encoded + * @param time: a value that reflects a time + * @param returnDigits: number of digits to return + * + * @return: a numeric String in base 10 that includes + * {@link truncationDigits} digits + */ + + public static String generateTOTP256(String key, + String time, + String returnDigits){ + return generateTOTP(key, time, returnDigits, "HmacSHA256"); + } + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 11] + +RFC 6238 HOTPTimeBased May 2011 + + + /** + * This method generates a TOTP value for the given + * set of parameters. + * + * @param key: the shared secret, HEX encoded + * @param time: a value that reflects a time + * @param returnDigits: number of digits to return + * + * @return: a numeric String in base 10 that includes + * {@link truncationDigits} digits + */ + + public static String generateTOTP512(String key, + String time, + String returnDigits){ + return generateTOTP(key, time, returnDigits, "HmacSHA512"); + } + + + /** + * This method generates a TOTP value for the given + * set of parameters. + * + * @param key: the shared secret, HEX encoded + * @param time: a value that reflects a time + * @param returnDigits: number of digits to return + * @param crypto: the crypto function to use + * + * @return: a numeric String in base 10 that includes + * {@link truncationDigits} digits + */ + + public static String generateTOTP(String key, + String time, + String returnDigits, + String crypto){ + int codeDigits = Integer.decode(returnDigits).intValue(); + String result = null; + + // Using the counter + // First 8 bytes are for the movingFactor + // Compliant with base RFC 4226 (HOTP) + while (time.length() < 16 ) + time = "0" + time; + + // Get the HEX in a Byte[] + byte[] msg = hexStr2Bytes(time); + byte[] k = hexStr2Bytes(key); + + + +M'Raihi, et al. Informational [Page 12] + +RFC 6238 HOTPTimeBased May 2011 + + + byte[] hash = hmac_sha(crypto, k, msg); + + // put selected bytes into result int + int offset = hash[hash.length - 1] & 0xf; + + int binary = + ((hash[offset] & 0x7f) << 24) | + ((hash[offset + 1] & 0xff) << 16) | + ((hash[offset + 2] & 0xff) << 8) | + (hash[offset + 3] & 0xff); + + int otp = binary % DIGITS_POWER[codeDigits]; + + result = Integer.toString(otp); + while (result.length() < codeDigits) { + result = "0" + result; + } + return result; + } + + public static void main(String[] args) { + // Seed for HMAC-SHA1 - 20 bytes + String seed = "3132333435363738393031323334353637383930"; + // Seed for HMAC-SHA256 - 32 bytes + String seed32 = "3132333435363738393031323334353637383930" + + "313233343536373839303132"; + // Seed for HMAC-SHA512 - 64 bytes + String seed64 = "3132333435363738393031323334353637383930" + + "3132333435363738393031323334353637383930" + + "3132333435363738393031323334353637383930" + + "31323334"; + long T0 = 0; + long X = 30; + long testTime[] = {59L, 1111111109L, 1111111111L, + 1234567890L, 2000000000L, 20000000000L}; + + String steps = "0"; + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 13] + +RFC 6238 HOTPTimeBased May 2011 + + + try { + System.out.println( + "+---------------+-----------------------+" + + "------------------+--------+--------+"); + System.out.println( + "| Time(sec) | Time (UTC format) " + + "| Value of T(Hex) | TOTP | Mode |"); + System.out.println( + "+---------------+-----------------------+" + + "------------------+--------+--------+"); + + for (int i=0; i + +Appendix B. Test Vectors + + This section provides test values that can be used for the HOTP time- + based variant algorithm interoperability test. + + + + + +M'Raihi, et al. Informational [Page 14] + +RFC 6238 HOTPTimeBased May 2011 + + + The test token shared secret uses the ASCII string value + "12345678901234567890". With Time Step X = 30, and the Unix epoch as + the initial value to count time steps, where T0 = 0, the TOTP + algorithm will display the following values for specified modes and + timestamps. + + +-------------+--------------+------------------+----------+--------+ + | Time (sec) | UTC Time | Value of T (hex) | TOTP | Mode | + +-------------+--------------+------------------+----------+--------+ + | 59 | 1970-01-01 | 0000000000000001 | 94287082 | SHA1 | + | | 00:00:59 | | | | + | 59 | 1970-01-01 | 0000000000000001 | 46119246 | SHA256 | + | | 00:00:59 | | | | + | 59 | 1970-01-01 | 0000000000000001 | 90693936 | SHA512 | + | | 00:00:59 | | | | + | 1111111109 | 2005-03-18 | 00000000023523EC | 07081804 | SHA1 | + | | 01:58:29 | | | | + | 1111111109 | 2005-03-18 | 00000000023523EC | 68084774 | SHA256 | + | | 01:58:29 | | | | + | 1111111109 | 2005-03-18 | 00000000023523EC | 25091201 | SHA512 | + | | 01:58:29 | | | | + | 1111111111 | 2005-03-18 | 00000000023523ED | 14050471 | SHA1 | + | | 01:58:31 | | | | + | 1111111111 | 2005-03-18 | 00000000023523ED | 67062674 | SHA256 | + | | 01:58:31 | | | | + | 1111111111 | 2005-03-18 | 00000000023523ED | 99943326 | SHA512 | + | | 01:58:31 | | | | + | 1234567890 | 2009-02-13 | 000000000273EF07 | 89005924 | SHA1 | + | | 23:31:30 | | | | + | 1234567890 | 2009-02-13 | 000000000273EF07 | 91819424 | SHA256 | + | | 23:31:30 | | | | + | 1234567890 | 2009-02-13 | 000000000273EF07 | 93441116 | SHA512 | + | | 23:31:30 | | | | + | 2000000000 | 2033-05-18 | 0000000003F940AA | 69279037 | SHA1 | + | | 03:33:20 | | | | + | 2000000000 | 2033-05-18 | 0000000003F940AA | 90698825 | SHA256 | + | | 03:33:20 | | | | + | 2000000000 | 2033-05-18 | 0000000003F940AA | 38618901 | SHA512 | + | | 03:33:20 | | | | + | 20000000000 | 2603-10-11 | 0000000027BC86AA | 65353130 | SHA1 | + | | 11:33:20 | | | | + | 20000000000 | 2603-10-11 | 0000000027BC86AA | 77737706 | SHA256 | + | | 11:33:20 | | | | + | 20000000000 | 2603-10-11 | 0000000027BC86AA | 47863826 | SHA512 | + | | 11:33:20 | | | | + +-------------+--------------+------------------+----------+--------+ + + Table 1: TOTP Table + + + +M'Raihi, et al. Informational [Page 15] + +RFC 6238 HOTPTimeBased May 2011 + + +Authors' Addresses + + David M'Raihi + Verisign, Inc. + 685 E. Middlefield Road + Mountain View, CA 94043 + USA + + EMail: davidietf@gmail.com + + + Salah Machani + Diversinet Corp. + 2225 Sheppard Avenue East, Suite 1801 + Toronto, Ontario M2J 5C2 + Canada + + EMail: smachani@diversinet.com + + + Mingliang Pei + Symantec + 510 E. Middlefield Road + Mountain View, CA 94043 + USA + + EMail: Mingliang_Pei@symantec.com + + + Johan Rydell + Portwise, Inc. + 275 Hawthorne Ave., Suite 119 + Palo Alto, CA 94301 + USA + + EMail: johanietf@gmail.com + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 16] + diff --git a/totp.go b/totp.go new file mode 100644 index 0000000..fe52c1c --- /dev/null +++ b/totp.go @@ -0,0 +1,533 @@ +package twofactor + +import ( + "bytes" + "code.google.com/p/rsc/qr" + "crypto" + "crypto/hmac" + "crypto/rand" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/base32" + "encoding/hex" + "errors" + "fmt" + "hash" + "io" + "net/url" + "strconv" + "time" +) + +const ( + BACKOFF_MINUTES = 5 // this is the time to wait before verifying another token + MAX_FAILURES = 3 // total amount of failures, after that the user needs to wait for the backoff time + COUNTER_SIZE = 8 +) + +type totp struct { + key []byte // this is the secret key + counter [COUNTER_SIZE]byte // this is the counter used to synchronize with the client device + digits int // total amount of digits of the code displayed on the device + issuer string // the company which issues the 2FA + account string // usually the suer email or the account id + stepSize int // by default 30 seconds + clientOffset int // the amount of steps the client is off + totalVerificationFailures int // the total amount of verification failures from the client - by default 10 + lastVerificationTime time.Time // the last verification executed + hashFunction crypto.Hash // the hash function used in the HMAC construction (sha1 - sha156 - sha512) +} + +// This function is used to synchronize the counter with the client +// Offset can be a negative number as well +// Usually it's either -1, 0 or 1 +// This is used internally +func (otp *totp) synchronizeCounter(offset int) { + otp.clientOffset = offset +} + +// Label returns the combination of issuer:account string +func (otp *totp) label() string { + return url.QueryEscape(fmt.Sprintf("%s:%s", otp.issuer, otp.account)) +} + +// Counter returns the TOTP's 8-byte counter as unsigned 64-bit integer. +func (otp *totp) getIntCounter() uint64 { + return uint64FromBigEndian(otp.counter) +} + +// This function creates a new TOTP object +// This is the function which is needed to start the whole process +// account: usually the user email +// issuer: the name of the company/service +// hash: is the crypto function used: crypto.SHA1, crypto.SHA256, crypto.SHA512 +// digits: is the token amount of digits (6 or 7 or 8) +// steps: the amount of second the token is valid +// it autmatically generates a secret key using the golang crypto rand package. If there is not enough entropy the function returns an error +// The key is not encrypted in this package. It's a secret key. Therefore if you transfer the key bytes in the network, +// please take care of protecting the key or in fact all the bytes. +func NewTOTP(account, issuer string, hash crypto.Hash, digits int) (*totp, error) { + + keySize := hash.Size() + key := make([]byte, keySize) + total, err := rand.Read(key) + if err != nil { + return nil, errors.New(fmt.Sprintf("TOTP failed to create because there is not enough entropy, we got only %d random bytes", total)) + } + + // sanitize the digits range otherwise it may create invalid tokens ! + if digits < 6 || digits > 8 { + digits = 8 + } + + return makeTOTP(key, account, issuer, hash, digits) + +} + +// Private function which initialize the TOTP so that it's easier to unit test it +// Used internnaly +func makeTOTP(key []byte, account, issuer string, hash crypto.Hash, digits int) (*totp, error) { + otp := new(totp) + otp.key = key + otp.account = account + otp.issuer = issuer + otp.digits = digits + otp.stepSize = 30 // we set it to 30 seconds which is the recommended value from the RFC + otp.clientOffset = 0 + otp.hashFunction = hash + return otp, nil +} + +// This function validates the user privided token +// It calculates 3 different tokens. The current one, one before now and one after now. +// The difference is driven by the TOTP step size +// Based on which of the 3 steps it succeeds to validates, the client offset is updated. +// It also updates the total amount of verification failures and the last time a verification happened in UTC time +// Returns an error in case of verification failure, with the reason +// There is a very basic method which protects from timing attacks, although if the step time used is low it should not be necessary +// An attacker can still learn the synchronization offset. This is however irrelevant because the attacker has then 30 seconds to +// guess the code and after 3 failures the function returns an error for the following 5 minutes +func (otp *totp) Validate(userCode string) error { + + // verify that the token is valid + if userCode == "" { + return errors.New("User provided token is empty") + } + + // check against the total amount of failures + if otp.totalVerificationFailures >= MAX_FAILURES && !validBackoffTime(otp.lastVerificationTime) { + return errors.New("The verification is locked down, because of too many trials.") + } + + if otp.totalVerificationFailures >= MAX_FAILURES && validBackoffTime(otp.lastVerificationTime) { + // reset the total verification failures counter + otp.totalVerificationFailures = 0 + } + + // calculate the sha256 of the user code + userTokenHash := sha256.Sum256([]byte(userCode)) + userToken := hex.EncodeToString(userTokenHash[:]) + + // 1 calculate the 3 tokens + tokens := make([]string, 3) + token0Hash := sha256.Sum256([]byte(calculateTOTP(otp, -1))) + token1Hash := sha256.Sum256([]byte(calculateTOTP(otp, 0))) + token2Hash := sha256.Sum256([]byte(calculateTOTP(otp, 1))) + tokens[0] = hex.EncodeToString(token0Hash[:]) // sha256.Sum256() // 30 seconds ago token + tokens[1] = hex.EncodeToString(token1Hash[:]) // current token + tokens[2] = hex.EncodeToString(token2Hash[:]) // next 30 seconds token + + // if the current time token is valid then, no need to re-sync and return nil + if tokens[1] == userToken { + return nil + } + + // if the let's say 30 seconds ago token is valid then return nil, but re-synchronize + if tokens[0] == userToken { + otp.synchronizeCounter(-1) + return nil + } + + // if the let's say 30 seconds ago token is valid then return nil, but re-synchronize + if tokens[2] == userToken { + otp.synchronizeCounter(1) + return nil + } + + otp.totalVerificationFailures++ + otp.lastVerificationTime = time.Now().UTC() // important to have it in UTC + + // if we got here everything is good + return errors.New("Tokens mismatch.") +} + +// Checks the time difference between the function call time and the parameter +// if the difference of time is greater than BACKOFF_MINUTES it returns true, otherwise false +func validBackoffTime(lastVerification time.Time) bool { + diff := lastVerification.UTC().Add(BACKOFF_MINUTES * time.Minute) + return time.Now().UTC().After(diff) +} + +// Basically, we define TOTP as TOTP = HOTP(K, T), where T is an integer +// and represents the number of time steps between the initial counter +// time T0 and the current Unix time. +// T = (Current Unix time - T0) / X, where the +// default floor function is used in the computation. +// For example, with T0 = 0 and Time Step X = 30, T = 1 if the current +// Unix time is 59 seconds, and T = 2 if the current Unix time is +// 60 seconds. +func (otp *totp) incrementCounter(index int) { + // Unix returns t as a Unix time, the number of seconds elapsed since January 1, 1970 UTC. + counterOffset := time.Duration(index*otp.stepSize) * time.Second + clientOffset := time.Duration(otp.clientOffset*otp.stepSize) * time.Second + now := time.Now().UTC().Add(counterOffset).Add(clientOffset).Unix() + otp.counter = bigEndianUint64(increment(now, otp.stepSize)) +} + +// Function which calculates the value of T (see rfc6238) +func increment(ts int64, stepSize int) uint64 { + T := float64(ts / int64(stepSize)) // TODO: improve this conversions + n := round(T) // round T + return n // convert n to big endian byte array +} + +// Generates a new one time password with hmac-(HASH-FUNCTION) +func (otp *totp) OTP() string { + // it uses the index 0, meaning that it calculates the current one + return calculateTOTP(otp, 0) +} + +// Private function which calculates the OTP token based on the index offset +// example: 1 * steps or -1 * steps +func calculateTOTP(otp *totp, index int) string { + var h hash.Hash + + switch otp.hashFunction { + case crypto.SHA256: + h = hmac.New(sha256.New, otp.key) + break + case crypto.SHA512: + h = hmac.New(sha512.New, otp.key) + break + default: + h = hmac.New(sha1.New, otp.key) + break + + } + + // set the counter to the current step based ont he current time + // this is necessary to generate the proper OTP + otp.incrementCounter(index) + + return calculateToken(otp.counter[:], otp.digits, h) + +} + +func truncateHash(hmac_result []byte, size int) int64 { + offset := hmac_result[size-1] & 0xf + bin_code := (uint32(hmac_result[offset])&0x7f)<<24 | + (uint32(hmac_result[offset+1])&0xff)<<16 | + (uint32(hmac_result[offset+2])&0xff)<<8 | + (uint32(hmac_result[offset+3]) & 0xff) + return int64(bin_code) +} + +// this is the function which calculates the HTOP code +func calculateToken(counter []byte, digits int, h hash.Hash) string { + + h.Write(counter) + hashResult := h.Sum(nil) + result := truncateHash(hashResult, h.Size()) + var mod uint64 + if digits == 8 { + mod = uint64(result % 100000000) + } + + if digits == 7 { + mod = uint64(result % 10000000) + } + + if digits == 6 { + mod = uint64(result % 1000000) + } + + fmtStr := fmt.Sprintf("%%0%dd", digits) + return fmt.Sprintf(fmtStr, mod) +} + +// URL returns a suitable URL, such as for the Google Authenticator app +// example: otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example +func (otp *totp) URL() string { + secret := base32.StdEncoding.EncodeToString(otp.key) + u := url.URL{} + v := url.Values{} + u.Scheme = "otpauth" + u.Host = "totp" + u.Path = otp.label() + v.Add("secret", secret) + v.Add("counter", fmt.Sprintf("%d", otp.getIntCounter())) + v.Add("issuer", otp.issuer) + v.Add("digits", strconv.Itoa(otp.digits)) + v.Add("period", strconv.Itoa(otp.stepSize)) + switch otp.hashFunction { + case crypto.SHA256: + v.Add("algorithm", "SHA256") + break + case crypto.SHA512: + v.Add("algorithm", "SHA512") + break + default: + v.Add("algorithm", "SHA1") + break + } + u.RawQuery = v.Encode() + return u.String() +} + +// QR generates a byte array containing QR code encoded PNG image, with level Q error correction, +// needed for the client apps to generate tokens +// The QR code should be displayed only the first time the user enabled the Two-Factor authentication. +// The QR code contains the shared KEY between the server application and the client application, +// therefore the QR code should be delivered via secure connection. +func (otp *totp) QR() ([]byte, error) { + u := otp.URL() + code, err := qr.Encode(u, qr.Q) + if err != nil { + return nil, err + } + return code.PNG(), nil +} + +// ToBytes serialises a TOTP object in a byte array +// Sizes: 4 4 N 8 4 4 N 4 N 4 4 4 8 4 +// Format: |total_bytes|key_size|key|counter|digits|issuer_size|issuer|account_size|account|steps|offset|total_failures|verification_time|hashFunction_type| +// hashFunction_type: 0 = SHA1; 1 = SHA256; 2 = SHA512 +// TODO: +// 1- improve sizes. For instance the hashFunction_type could be a short. +// 2- Encrypt the key, in case it's transferred in the network unsafely +func (otp *totp) ToBytes() ([]byte, error) { + var buffer bytes.Buffer + + // caluclate the length of the key and create its byte representation + keySize := len(otp.key) + keySizeBytes := bigEndianInt(keySize) + + // caluclate the length of the issuer and create its byte representation + issuerSize := len(otp.issuer) + issuerSizeBytes := bigEndianInt(issuerSize) + + // caluclate the length of the account and create its byte representation + accountSize := len(otp.account) + accountSizeBytes := bigEndianInt(accountSize) + + totalSize := 4 + 4 + keySize + 8 + 4 + 4 + issuerSize + 4 + accountSize + 4 + 4 + 4 + 8 + 4 + totalSizeBytes := bigEndianInt(totalSize) + + // at this point we are ready to write the data to the byte buffer + // total size + if _, err := buffer.Write(totalSizeBytes[:]); err != nil { + return nil, err + } + + // key + if _, err := buffer.Write(keySizeBytes[:]); err != nil { + return nil, err + } + if _, err := buffer.Write(otp.key); err != nil { + return nil, err + } + + // counter + counterBytes := bigEndianUint64(otp.getIntCounter()) + if _, err := buffer.Write(counterBytes[:]); err != nil { + return nil, err + } + + // digits + digitBytes := bigEndianInt(otp.digits) + if _, err := buffer.Write(digitBytes[:]); err != nil { + return nil, err + } + + // issuer + if _, err := buffer.Write(issuerSizeBytes[:]); err != nil { + return nil, err + } + if _, err := buffer.WriteString(otp.issuer); err != nil { + return nil, err + } + + // account + if _, err := buffer.Write(accountSizeBytes[:]); err != nil { + return nil, err + } + if _, err := buffer.WriteString(otp.account); err != nil { + return nil, err + } + + // steps + stepsBytes := bigEndianInt(otp.stepSize) + if _, err := buffer.Write(stepsBytes[:]); err != nil { + return nil, err + } + + // offset + offsetBytes := bigEndianInt(otp.clientOffset) + if _, err := buffer.Write(offsetBytes[:]); err != nil { + return nil, err + } + + // total_failures + totalFailuresBytes := bigEndianInt(otp.totalVerificationFailures) + if _, err := buffer.Write(totalFailuresBytes[:]); err != nil { + return nil, err + } + + // last verification time + verificationTimeBytes := bigEndianUint64(uint64(otp.lastVerificationTime.Unix())) + if _, err := buffer.Write(verificationTimeBytes[:]); err != nil { + return nil, err + } + + // has_function_type + switch otp.hashFunction { + case crypto.SHA256: + sha256Bytes := bigEndianInt(1) + if _, err := buffer.Write(sha256Bytes[:]); err != nil { + return nil, err + } + break + case crypto.SHA512: + sha512Bytes := bigEndianInt(2) + if _, err := buffer.Write(sha512Bytes[:]); err != nil { + return nil, err + } + break + default: + sha1Bytes := bigEndianInt(0) + if _, err := buffer.Write(sha1Bytes[:]); err != nil { + return nil, err + } + } + + //fmt.Println("Total bytes", len(buffer.Bytes())) + return buffer.Bytes(), nil + +} + +// TOTPFromBytes converts a byte array to a totp object +// it stores the state of the TOTP object, like the key, the current counter, the client offset, +// the total amount of verification failures and the last time a verification happened +func TOTPFromBytes(data []byte) (*totp, error) { + // fmt.Println("Bytes", len(data)) + // new reader + reader := bytes.NewReader(data) + + // otp object + otp := new(totp) + + // get the lenght + lenght := make([]byte, 4) + _, err := reader.Read(lenght) // read the 4 bytes for the total lenght + if err != nil && err != io.EOF { + return otp, err + } + + totalSize := intFromBigEndian([4]byte{lenght[0], lenght[1], lenght[2], lenght[3]}) + buffer := make([]byte, totalSize-4) + _, err = reader.Read(buffer) + if err != nil && err != io.EOF { + return otp, err + } + + // skip the total bytes size + startOffset := 0 + // read key size + endOffset := startOffset + 4 + keyBytes := buffer[startOffset:endOffset] + keySize := intFromBigEndian([4]byte{keyBytes[0], keyBytes[1], keyBytes[2], keyBytes[3]}) + + // read the key + startOffset = endOffset + endOffset = startOffset + keySize + otp.key = buffer[startOffset:endOffset] + + // read the counter + startOffset = endOffset + endOffset = startOffset + 8 + b := buffer[startOffset:endOffset] + otp.counter = [8]byte{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]} + + // read the digits + startOffset = endOffset + endOffset = startOffset + 4 + b = buffer[startOffset:endOffset] + otp.digits = intFromBigEndian([4]byte{b[0], b[1], b[2], b[3]}) // + + // read the issuer size + startOffset = endOffset + endOffset = startOffset + 4 + b = buffer[startOffset:endOffset] + issuerSize := intFromBigEndian([4]byte{b[0], b[1], b[2], b[3]}) + + // read the issuer string + startOffset = endOffset + endOffset = startOffset + issuerSize + otp.issuer = string(buffer[startOffset:endOffset]) + + // read the account size + startOffset = endOffset + endOffset = startOffset + 4 + b = buffer[startOffset:endOffset] + accountSize := intFromBigEndian([4]byte{b[0], b[1], b[2], b[3]}) + + // read the account string + startOffset = endOffset + endOffset = startOffset + accountSize + otp.account = string(buffer[startOffset:endOffset]) + + // read the steps + startOffset = endOffset + endOffset = startOffset + 4 + b = buffer[startOffset:endOffset] + otp.stepSize = intFromBigEndian([4]byte{b[0], b[1], b[2], b[3]}) + + // read the offset + startOffset = endOffset + endOffset = startOffset + 4 + b = buffer[startOffset:endOffset] + otp.clientOffset = intFromBigEndian([4]byte{b[0], b[1], b[2], b[3]}) + + // read the total failuers + startOffset = endOffset + endOffset = startOffset + 4 + b = buffer[startOffset:endOffset] + otp.totalVerificationFailures = intFromBigEndian([4]byte{b[0], b[1], b[2], b[3]}) + + // read the offset + startOffset = endOffset + endOffset = startOffset + 8 + b = buffer[startOffset:endOffset] + ts := uint64FromBigEndian([8]byte{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]}) + otp.lastVerificationTime = time.Unix(int64(ts), 0) + + // read the hash type + startOffset = endOffset + endOffset = startOffset + 4 + b = buffer[startOffset:endOffset] + hashType := intFromBigEndian([4]byte{b[0], b[1], b[2], b[3]}) + + switch hashType { + case 1: + otp.hashFunction = crypto.SHA256 + break + case 2: + otp.hashFunction = crypto.SHA512 + break + default: + otp.hashFunction = crypto.SHA1 + } + + return otp, err +} diff --git a/totp_test.go b/totp_test.go new file mode 100644 index 0000000..c0c6f0a --- /dev/null +++ b/totp_test.go @@ -0,0 +1,288 @@ +package twofactor + +import ( + "bytes" + "crypto" + "crypto/hmac" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "encoding/hex" + "net/url" + "testing" + "time" +) + +var sha1KeyHex = "3132333435363738393031323334353637383930" +var sha256KeyHex = "3132333435363738393031323334353637383930313233343536373839303132" +var sha512KeyHex = "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334" + +var sha1TestData = []string{ + "94287082", + "07081804", + "14050471", + "89005924", + "69279037", + "65353130", +} + +var sha256TestData = []string{ + "46119246", + "68084774", + "67062674", + "91819424", + "90698825", + "77737706", +} + +var sha512TestData = []string{ + "90693936", + "25091201", + "99943326", + "93441116", + "38618901", + "47863826", +} + +var timeCounters = []int64{ + int64(59), // 1970-01-01 00:00:59 + int64(1111111109), // 2005-03-18 01:58:29 + int64(1111111111), // 2005-03-18 01:58:31 + int64(1234567890), // 2009-02-13 23:31:30 + int64(2000000000), // 2033-05-18 03:33:20 + int64(20000000000), // 2603-10-11 11:33:20 +} + +func checkError(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } +} + +func TestTOTP(t *testing.T) { + + keySha1, err := hex.DecodeString(sha1KeyHex) + checkError(t, err) + + keySha256, err := hex.DecodeString(sha256KeyHex) + checkError(t, err) + + keySha512, err := hex.DecodeString(sha512KeyHex) + checkError(t, err) + + // create the OTP + otp := new(totp) + otp.digits = 8 + otp.issuer = "Sec51" + otp.account = "no-reply@sec51.com" + + // Test SHA1 + otp.key = keySha1 + for index, ts := range timeCounters { + counter := increment(ts, 30) + otp.counter = bigEndianUint64(counter) + hash := hmac.New(sha1.New, otp.key) + token := calculateToken(otp.counter[:], otp.digits, hash) + expected := sha1TestData[index] + if token != expected { + t.Errorf("SHA1 test data, token mismatch. Got %s, expected %s\n", token, expected) + } + } + + // Test SHA256 + otp.key = keySha256 + for index, ts := range timeCounters { + counter := increment(ts, 30) + otp.counter = bigEndianUint64(counter) + hash := hmac.New(sha256.New, otp.key) + token := calculateToken(otp.counter[:], otp.digits, hash) + expected := sha256TestData[index] + if token != expected { + t.Errorf("SHA256 test data, token mismatch. Got %s, expected %s\n", token, expected) + } + } + + // Test SHA512 + otp.key = keySha512 + for index, ts := range timeCounters { + counter := increment(ts, 30) + otp.counter = bigEndianUint64(counter) + hash := hmac.New(sha512.New, otp.key) + token := calculateToken(otp.counter[:], otp.digits, hash) + expected := sha512TestData[index] + if token != expected { + t.Errorf("SHA512 test data, token mismatch. Got %s, expected %s\n", token, expected) + } + } + +} + +func TestVerificationFailures(t *testing.T) { + + otp, err := NewTOTP("info@sec51.com", "Sec51", crypto.SHA1, 7) + //checkError(t, err) + if err != nil { + t.Fatal(err) + } + + // generate a new token + expectedToken := otp.OTP() + + //verify the new token + if err := otp.Validate(expectedToken); err != nil { + t.Fatal(err) + } + + // verify the wrong token for 3 times and check the internal counters values + for i := 0; i < 3; i++ { + if err := otp.Validate("1234567"); err == nil { + t.Fatal(err) + } + } + + if otp.totalVerificationFailures != 3 { + t.Errorf("Expected 3 verifcation failures, instead we've got %d\n", otp.totalVerificationFailures) + } + + // at this point we crossed the max failures, therefore it should always return an error + for i := 0; i < 10; i++ { + if err := otp.Validate(expectedToken); err == nil { + t.Fatal(err) + } + } + + // set the lastVerificationTime ahead in the future. + // it should at this point pass + back10Minutes := time.Duration(-10) * time.Minute + otp.lastVerificationTime = time.Now().UTC().Add(back10Minutes) + + for i := 0; i < 10; i++ { + if err := otp.Validate(expectedToken); err != nil { + t.Fatal(err) + } + + if i == 0 { + // at this point the max failure counter should have been reset to zero + if otp.totalVerificationFailures != 0 { + t.Errorf("totalVerificationFailures counter not reset to zero. We've got: %d\n", otp.totalVerificationFailures) + } + } + } + +} + +func TestIncrementCounter(t *testing.T) { + + ts := int64(1438601387) + unixTime := time.Unix(ts, 0).UTC() + // DEBUG + // fmt.Println(time.Unix(ts, 0).UTC().Format(time.RFC1123)) + result := increment(unixTime.Unix(), 30) + expected := uint64(47953379) + if result != expected { + t.Fatal("Error incrementing counter") + } + +} + +func TestSerialization(t *testing.T) { + // create a new TOTP + otp, err := NewTOTP("info@sec51.com", "Sec51", crypto.SHA512, 8) + if err != nil { + t.Fatal(err) + } + + // set some properties to a value different than the default + otp.totalVerificationFailures = 2 + otp.stepSize = 27 + otp.lastVerificationTime = time.Now().UTC() + otp.clientOffset = 1 + + // Serialize it to bytes + otpData, err := otp.ToBytes() + if err != nil { + t.Fatal(err) + } + + // Convert it back from bytes to TOTP + deserializedOTP, err := TOTPFromBytes(otpData) + if err != nil { + t.Fatal(err) + } + + deserializedOTPData, err := deserializedOTP.ToBytes() + if err != nil { + t.Fatal(err) + } + + if deserializedOTP == nil { + t.Error("Could not deserialize back the TOTP object from bytes") + } + + if bytes.Compare(deserializedOTP.key, otp.key) != 0 { + t.Error("Deserialized digits property differ from original TOTP") + } + + if deserializedOTP.digits != otp.digits { + t.Error("Deserialized digits property differ from original TOTP") + } + + if deserializedOTP.totalVerificationFailures != otp.totalVerificationFailures { + t.Error("Deserialized totalVerificationFailures property differ from original TOTP") + } + + if deserializedOTP.stepSize != otp.stepSize { + t.Error("Deserialized stepSize property differ from original TOTP") + } + + if deserializedOTP.lastVerificationTime.Unix() != otp.lastVerificationTime.Unix() { + t.Error("Deserialized lastVerificationTime property differ from original TOTP") + } + + if deserializedOTP.getIntCounter() != otp.getIntCounter() { + t.Error("Deserialized counter property differ from original TOTP") + } + + if deserializedOTP.clientOffset != otp.clientOffset { + t.Error("Deserialized clientOffset property differ from original TOTP") + } + + if deserializedOTP.account != otp.account { + t.Error("Deserialized account property differ from original TOTP") + } + + if deserializedOTP.issuer != otp.issuer { + t.Error("Deserialized issuer property differ from original TOTP") + } + + if deserializedOTP.OTP() != otp.OTP() { + t.Error("Deserialized OTP token property differ from original TOTP") + } + + if deserializedOTP.hashFunction != otp.hashFunction { + t.Error("Deserialized hash property differ from original TOTP") + } + + if deserializedOTP.URL() != otp.URL() { + t.Error("Deserialized URL property differ from original TOTP") + } + + if deserializedOTP.label() != otp.label() { + t.Error("Deserialized Label property differ from original TOTP") + } + + if base64.StdEncoding.EncodeToString(otpData) != base64.StdEncoding.EncodeToString(deserializedOTPData) { + t.Error("Problems encoding TOTP to base64") + } + + label, err := url.QueryUnescape(otp.label()) + if err != nil { + t.Fatal(err) + } + + if label != "Sec51:info@sec51.com" { + t.Error("Creation of TOTP Label failed") + } + +}