├── .gitignore ├── .travis.yml ├── Godeps ├── Godeps.json └── Readme ├── LICENSE ├── README.md ├── doc.go ├── glide.lock ├── glide.yaml ├── rfc4226.txt ├── rfc6238.txt ├── totp.go └── totp_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | keys/ 2 | vendor/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.6.2 5 | 6 | install: 7 | - go get "github.com/sec51/qrcode" 8 | - go get "github.com/sec51/cryptoengine" 9 | - go get "github.com/sec51/convert/smallendian" 10 | 11 | script: 12 | - go test -v ./... 13 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/sec51/twofactor", 3 | "GoVersion": "go1.6", 4 | "GodepVersion": "v74", 5 | "Deps": [ 6 | { 7 | "ImportPath": "github.com/sec51/convert", 8 | "Rev": "8ed1f399b5e0a9a9620c74cfd5aec3682d8328ab" 9 | }, 10 | { 11 | "ImportPath": "github.com/sec51/convert/bigendian", 12 | "Rev": "8ed1f399b5e0a9a9620c74cfd5aec3682d8328ab" 13 | }, 14 | { 15 | "ImportPath": "github.com/sec51/convert/smallendian", 16 | "Rev": "8ed1f399b5e0a9a9620c74cfd5aec3682d8328ab" 17 | }, 18 | { 19 | "ImportPath": "github.com/sec51/cryptoengine", 20 | "Rev": "11617a465c082a1e82359b3c059f018f8dcbfc93" 21 | }, 22 | { 23 | "ImportPath": "github.com/sec51/gf256", 24 | "Rev": "2454accbeb9e6b0e2e53b01e1d641c7157251ed4" 25 | }, 26 | { 27 | "ImportPath": "github.com/sec51/qrcode", 28 | "Rev": "b7779abbcaf1ec4de65f586a85fe24db31d45e7c" 29 | }, 30 | { 31 | "ImportPath": "github.com/sec51/qrcode/coding", 32 | "Rev": "b7779abbcaf1ec4de65f586a85fe24db31d45e7c" 33 | }, 34 | { 35 | "ImportPath": "golang.org/x/crypto/curve25519", 36 | "Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc" 37 | }, 38 | { 39 | "ImportPath": "golang.org/x/crypto/hkdf", 40 | "Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc" 41 | }, 42 | { 43 | "ImportPath": "golang.org/x/crypto/nacl/box", 44 | "Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc" 45 | }, 46 | { 47 | "ImportPath": "golang.org/x/crypto/nacl/secretbox", 48 | "Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc" 49 | }, 50 | { 51 | "ImportPath": "golang.org/x/crypto/poly1305", 52 | "Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc" 53 | }, 54 | { 55 | "ImportPath": "golang.org/x/crypto/salsa20/salsa", 56 | "Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc" 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Sec51.com 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Current test status 2 | 3 | [![Build Status](https://travis-ci.org/sec51/twofactor.svg?branch=master)](https://travis-ci.org/sec51/twofactor.svg?branch=master) 4 | [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/sec51/twofactor/) 5 | 6 | ## `totp` 7 | 8 | This package implements the RFC 6238 OATH-TOTP algorithm; 9 | 10 | ### Installation 11 | 12 | ```go get github.com/sec51/twofactor``` 13 | 14 | ### Features 15 | 16 | * Built-in support for secure crypto keys generation 17 | 18 | * Built in encryption of the secret keys when converted to bytes, so that they can be safely transmitted over the network, or stored in a DB 19 | 20 | * Built-in back-off time when a user fails to authenticate more than 3 times 21 | 22 | * Bult-in serialization and deserialization to store the one time token struct in a persistence layer 23 | 24 | * Automatic re-synchronization with the client device 25 | 26 | * Built-in generation of a PNG QR Code for adding easily the secret key on the user device 27 | 28 | * Supports 6, 7, 8 digits tokens 29 | 30 | * Supports HMAC-SHA1, HMAC-SHA256, HMAC-SHA512 31 | 32 | 33 | ### Storing Keys 34 | 35 | > The key is created using Golang crypto random function. It's a **secret key** and therefore 36 | > it needs to be **protected against unauthorized access**. The key cannot be leaked, otherwise the security is completely compromised. 37 | > The key is presented to the user in a form of QR Code. Once scanned the key should never be revealed again. 38 | > In addition when the QR code is shared with the client for scanning, the connection used must be secured (HTTPS). 39 | 40 | The `totp` struct can be easily serialized using the `ToBytes()` function. 41 | The bytes can then be stored on a persistent layer (database for example). The bytes are encrypted using `cryptoengine` library (NaCl) 42 | You can then retrieve the object back with the function: `TOTPFromBytes` 43 | 44 | > You can transfer the bytes securely via a network connection (Ex. if the database is in a different server) because they are encrypted and authenticated. 45 | 46 | The struct needs to be stored in a persistent layer becase its values, like last token verification time, 47 | max user authentication failures, etc.. need to be preserved. 48 | The secret key needs to be preserved too, between the user accound and the user device. 49 | The secret key is in fact used to derive tokens. 50 | 51 | ### Upcoming features 52 | 53 | * Generation of recovery tokens. 54 | 55 | * Integration with Twilio for sending the token via SMS, in case the user loses its entry in the Google authenticator app. 56 | 57 | 58 | ### Example Usages 59 | 60 | #### Case 1: Google Authenticator 61 | 62 | * How to use the library 63 | 64 | 1- Import the library 65 | 66 | ``` 67 | import github.com/sec51/twofactor 68 | ``` 69 | 70 | 2- Instanciate the `totp` object via: 71 | 72 | ``` 73 | otp, err := twofactor.NewTOTP("info@sec51.com", "Sec51", crypto.SHA1, 8) 74 | if err != nil { 75 | return err 76 | } 77 | ``` 78 | 79 | 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 80 | 81 | ``` 82 | qrBytes, err := otp.QR() 83 | if err != nil { 84 | return err 85 | } 86 | ``` 87 | 88 | 4- Verify the user provided token, coming from the google authenticator app 89 | 90 | ``` 91 | err := otp.Validate(USER_PROVIDED_TOKEN) 92 | if err != nil { 93 | return err 94 | } 95 | // if there is an error, then the authentication failed 96 | // if it succeeded, then store this information and do not display the QR code ever again. 97 | ``` 98 | 99 | 5- All following authentications should display only a input field with no QR code. 100 | 101 | 102 | ### References 103 | 104 | * [RFC 6238 - *TOTP: Time-Based One-Time Password Algorithm*](https://tools.ietf.org/rfc/rfc6238.txt) 105 | 106 | * The [Key URI Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format) 107 | 108 | 109 | ### Author 110 | 111 | `totp` was written by Sec51 . 112 | 113 | 114 | ### License 115 | 116 | ``` 117 | Copyright (c) 2015 Sec51.com 118 | 119 | Permission to use, copy, modify, and distribute this software for any 120 | purpose with or without fee is hereby granted, provided that the above 121 | copyright notice and this permission notice appear in all copies. 122 | 123 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 124 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 125 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 126 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 127 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 128 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 129 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 130 | ``` 131 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | The package twofactor implements the RFC 6238 TOTP: Time-Based One-Time Password Algorithm 3 | 4 | The library provides a simple and secure way to generate and verify the OTP tokens 5 | and provides the possibility to display QR codes out of the box 6 | 7 | The library supports HMAC-SHA1, HMAC-SHA256, HMAC-SHA512 8 | */ 9 | package twofactor 10 | 11 | /* 12 | Copyright (c) 2015 Sec51 13 | 14 | Permission to use, copy, modify, and distribute this software for any 15 | purpose with or without fee is hereby granted, provided that the above 16 | copyright notice and this permission notice appear in all copies. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 19 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 20 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 21 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 22 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 23 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 24 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 25 | */ 26 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: edc113943b5834aa52876ee0bdeac172678a94416ed1f3ed8da78afbff402d89 2 | updated: 2018-09-11T13:25:32.886071+02:00 3 | imports: 4 | - name: github.com/sec51/convert 5 | version: 3276ac712ca35cb9cc9a823b564fdaf89f4ac803 6 | subpackages: 7 | - bigendian 8 | - smallendian 9 | - name: github.com/sec51/cryptoengine 10 | version: 2306d105a49ec564d9d376570a1881d557fc4a82 11 | - name: github.com/sec51/gf256 12 | version: 2454accbeb9e6b0e2e53b01e1d641c7157251ed4 13 | - name: github.com/sec51/qrcode 14 | version: b7779abbcaf1ec4de65f586a85fe24db31d45e7c 15 | subpackages: 16 | - coding 17 | - name: golang.org/x/crypto 18 | version: beef0f4390813b96e8e68fd78570396d0f4751fc 19 | subpackages: 20 | - curve25519 21 | - hkdf 22 | - nacl/box 23 | - nacl/secretbox 24 | - poly1305 25 | - salsa20/salsa 26 | testImports: [] 27 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/sec51/twofactor 2 | import: 3 | - package: github.com/sec51/convert 4 | version: 1.0.1 5 | subpackages: 6 | - bigendian 7 | - smallendian 8 | - package: github.com/sec51/cryptoengine 9 | version: 0.0.1 10 | - package: github.com/sec51/gf256 11 | version: 2454accbeb9e6b0e2e53b01e1d641c7157251ed4 12 | - package: github.com/sec51/qrcode 13 | version: b7779abbcaf1ec4de65f586a85fe24db31d45e7c 14 | subpackages: 15 | - coding 16 | - package: golang.org/x/crypto 17 | version: beef0f4390813b96e8e68fd78570396d0f4751fc 18 | subpackages: 19 | - curve25519 20 | - hkdf 21 | - nacl/box 22 | - nacl/secretbox 23 | - poly1305 24 | - salsa20/salsa 25 | -------------------------------------------------------------------------------- /rfc4226.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group D. M'Raihi 8 | Request for Comments: 4226 VeriSign 9 | Category: Informational M. Bellare 10 | UCSD 11 | F. Hoornaert 12 | Vasco 13 | D. Naccache 14 | Gemplus 15 | O. Ranen 16 | Aladdin 17 | December 2005 18 | 19 | 20 | HOTP: An HMAC-Based One-Time Password Algorithm 21 | 22 | Status of This Memo 23 | 24 | This memo provides information for the Internet community. It does 25 | not specify an Internet standard of any kind. Distribution of this 26 | memo is unlimited. 27 | 28 | Copyright Notice 29 | 30 | Copyright (C) The Internet Society (2005). 31 | 32 | Abstract 33 | 34 | This document describes an algorithm to generate one-time password 35 | values, based on Hashed Message Authentication Code (HMAC). A 36 | security analysis of the algorithm is presented, and important 37 | parameters related to the secure deployment of the algorithm are 38 | discussed. The proposed algorithm can be used across a wide range of 39 | network applications ranging from remote Virtual Private Network 40 | (VPN) access, Wi-Fi network logon to transaction-oriented Web 41 | applications. 42 | 43 | This work is a joint effort by the OATH (Open AuTHentication) 44 | membership to specify an algorithm that can be freely distributed to 45 | the technical community. The authors believe that a common and 46 | shared algorithm will facilitate adoption of two-factor 47 | authentication on the Internet by enabling interoperability across 48 | commercial and open-source implementations. 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | M'Raihi, et al. Informational [Page 1] 59 | 60 | RFC 4226 HOTP Algorithm December 2005 61 | 62 | 63 | Table of Contents 64 | 65 | 1. Overview ........................................................3 66 | 2. Introduction ....................................................3 67 | 3. Requirements Terminology ........................................4 68 | 4. Algorithm Requirements ..........................................4 69 | 5. HOTP Algorithm ..................................................5 70 | 5.1. Notation and Symbols .......................................5 71 | 5.2. Description ................................................6 72 | 5.3. Generating an HOTP Value ...................................6 73 | 5.4. Example of HOTP Computation for Digit = 6 ..................7 74 | 6. Security Considerations .........................................8 75 | 7. Security Requirements ...........................................9 76 | 7.1. Authentication Protocol Requirements .......................9 77 | 7.2. Validation of HOTP Values .................................10 78 | 7.3. Throttling at the Server ..................................10 79 | 7.4. Resynchronization of the Counter ..........................11 80 | 7.5. Management of Shared Secrets ..............................11 81 | 8. Composite Shared Secrets .......................................14 82 | 9. Bi-Directional Authentication ..................................14 83 | 10. Conclusion ....................................................15 84 | 11. Acknowledgements ..............................................15 85 | 12. Contributors ..................................................15 86 | 13. References ....................................................15 87 | 13.1. Normative References .....................................15 88 | 13.2. Informative References ...................................16 89 | Appendix A - HOTP Algorithm Security: Detailed Analysis ...........17 90 | A.1. Definitions and Notations .................................17 91 | A.2. The Idealized Algorithm: HOTP-IDEAL .......................17 92 | A.3. Model of Security .........................................18 93 | A.4. Security of the Ideal Authentication Algorithm ............19 94 | A.4.1. From Bits to Digits ................................19 95 | A.4.2. Brute Force Attacks ................................21 96 | A.4.3. Brute force attacks are the best possible attacks ..22 97 | A.5. Security Analysis of HOTP .................................23 98 | Appendix B - SHA-1 Attacks ........................................25 99 | B.1. SHA-1 Status ..............................................25 100 | B.2. HMAC-SHA-1 Status .........................................26 101 | B.3. HOTP Status ...............................................26 102 | Appendix C - HOTP Algorithm: Reference Implementation .............27 103 | Appendix D - HOTP Algorithm: Test Values ..........................32 104 | Appendix E - Extensions ...........................................33 105 | E.1. Number of Digits ..........................................33 106 | E.2. Alphanumeric Values .......................................33 107 | E.3. Sequence of HOTP values ...................................34 108 | E.4. A Counter-Based Resynchronization Method ..................34 109 | E.5. Data Field ................................................35 110 | 111 | 112 | 113 | 114 | M'Raihi, et al. Informational [Page 2] 115 | 116 | RFC 4226 HOTP Algorithm December 2005 117 | 118 | 119 | 1. Overview 120 | 121 | The document introduces first the context around an algorithm that 122 | generates one-time password values based on HMAC [BCK1] and, thus, is 123 | named the HMAC-Based One-Time Password (HOTP) algorithm. In Section 124 | 4, the algorithm requirements are listed and in Section 5, the HOTP 125 | algorithm is described. Sections 6 and 7 focus on the algorithm 126 | security. Section 8 proposes some extensions and improvements, and 127 | Section 10 concludes this document. In Appendix A, the interested 128 | reader will find a detailed, full-fledged analysis of the algorithm 129 | security: an idealized version of the algorithm is evaluated, and 130 | then the HOTP algorithm security is analyzed. 131 | 132 | 2. Introduction 133 | 134 | Today, deployment of two-factor authentication remains extremely 135 | limited in scope and scale. Despite increasingly higher levels of 136 | threats and attacks, most Internet applications still rely on weak 137 | authentication schemes for policing user access. The lack of 138 | interoperability among hardware and software technology vendors has 139 | been a limiting factor in the adoption of two-factor authentication 140 | technology. In particular, the absence of open specifications has 141 | led to solutions where hardware and software components are tightly 142 | coupled through proprietary technology, resulting in high-cost 143 | solutions, poor adoption, and limited innovation. 144 | 145 | In the last two years, the rapid rise of network threats has exposed 146 | the inadequacies of static passwords as the primary mean of 147 | authentication on the Internet. At the same time, the current 148 | approach that requires an end user to carry an expensive, single- 149 | function device that is only used to authenticate to the network is 150 | clearly not the right answer. For two-factor authentication to 151 | propagate on the Internet, it will have to be embedded in more 152 | flexible devices that can work across a wide range of applications. 153 | 154 | The ability to embed this base technology while ensuring broad 155 | interoperability requires that it be made freely available to the 156 | broad technical community of hardware and software developers. Only 157 | an open-system approach will ensure that basic two-factor 158 | authentication primitives can be built into the next generation of 159 | consumer devices such as USB mass storage devices, IP phones, and 160 | personal digital assistants. 161 | 162 | One-Time Password is certainly one of the simplest and most popular 163 | forms of two-factor authentication for securing network access. For 164 | example, in large enterprises, Virtual Private Network access often 165 | requires the use of One-Time Password tokens for remote user 166 | authentication. One-Time Passwords are often preferred to stronger 167 | 168 | 169 | 170 | M'Raihi, et al. Informational [Page 3] 171 | 172 | RFC 4226 HOTP Algorithm December 2005 173 | 174 | 175 | forms of authentication such as Public-Key Infrastructure (PKI) or 176 | biometrics because an air-gap device does not require the 177 | installation of any client desktop software on the user machine, 178 | therefore allowing them to roam across multiple machines including 179 | home computers, kiosks, and personal digital assistants. 180 | 181 | This document proposes a simple One-Time Password algorithm that can 182 | be implemented by any hardware manufacturer or software developer to 183 | create interoperable authentication devices and software agents. The 184 | algorithm is event-based so that it can be embedded in high-volume 185 | devices such as Java smart cards, USB dongles, and GSM SIM cards. 186 | The presented algorithm is made freely available to the developer 187 | community under the terms and conditions of the IETF Intellectual 188 | Property Rights [RFC3979]. 189 | 190 | The authors of this document are members of the Open AuTHentication 191 | initiative [OATH]. The initiative was created in 2004 to facilitate 192 | collaboration among strong authentication technology providers. 193 | 194 | 3. Requirements Terminology 195 | 196 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 197 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 198 | document are to be interpreted as described in [RFC2119]. 199 | 200 | 4. Algorithm Requirements 201 | 202 | This section presents the main requirements that drove this algorithm 203 | design. A lot of emphasis was placed on end-consumer usability as 204 | well as the ability for the algorithm to be implemented by low-cost 205 | hardware that may provide minimal user interface capabilities. In 206 | particular, the ability to embed the algorithm into high-volume SIM 207 | and Java cards was a fundamental prerequisite. 208 | 209 | R1 - The algorithm MUST be sequence- or counter-based: one of the 210 | goals is to have the HOTP algorithm embedded in high-volume devices 211 | such as Java smart cards, USB dongles, and GSM SIM cards. 212 | 213 | R2 - The algorithm SHOULD be economical to implement in hardware by 214 | minimizing requirements on battery, number of buttons, computational 215 | horsepower, and size of LCD display. 216 | 217 | R3 - The algorithm MUST work with tokens that do not support any 218 | numeric input, but MAY also be used with more sophisticated devices 219 | such as secure PIN-pads. 220 | 221 | R4 - The value displayed on the token MUST be easily read and entered 222 | by the user: This requires the HOTP value to be of reasonable length. 223 | 224 | 225 | 226 | M'Raihi, et al. Informational [Page 4] 227 | 228 | RFC 4226 HOTP Algorithm December 2005 229 | 230 | 231 | The HOTP value must be at least a 6-digit value. It is also 232 | desirable that the HOTP value be 'numeric only' so that it can be 233 | easily entered on restricted devices such as phones. 234 | 235 | R5 - There MUST be user-friendly mechanisms available to 236 | resynchronize the counter. Section 7.4 and Appendix E.4 details the 237 | resynchronization mechanism proposed in this document 238 | 239 | R6 - The algorithm MUST use a strong shared secret. The length of 240 | the shared secret MUST be at least 128 bits. This document 241 | RECOMMENDs a shared secret length of 160 bits. 242 | 243 | 5. HOTP Algorithm 244 | 245 | In this section, we introduce the notation and describe the HOTP 246 | algorithm basic blocks -- the base function to compute an HMAC-SHA-1 247 | value and the truncation method to extract an HOTP value. 248 | 249 | 5.1. Notation and Symbols 250 | 251 | A string always means a binary string, meaning a sequence of zeros 252 | and ones. 253 | 254 | If s is a string, then |s| denotes its length. 255 | 256 | If n is a number, then |n| denotes its absolute value. 257 | 258 | If s is a string, then s[i] denotes its i-th bit. We start numbering 259 | the bits at 0, so s = s[0]s[1]...s[n-1] where n = |s| is the length 260 | of s. 261 | 262 | Let StToNum (String to Number) denote the function that as input a 263 | string s returns the number whose binary representation is s. (For 264 | example, StToNum(110) = 6.) 265 | 266 | Here is a list of symbols used in this document. 267 | 268 | Symbol Represents 269 | ------------------------------------------------------------------- 270 | C 8-byte counter value, the moving factor. This counter 271 | MUST be synchronized between the HOTP generator (client) 272 | and the HOTP validator (server). 273 | 274 | K shared secret between client and server; each HOTP 275 | generator has a different and unique secret K. 276 | 277 | T throttling parameter: the server will refuse connections 278 | from a user after T unsuccessful authentication attempts. 279 | 280 | 281 | 282 | M'Raihi, et al. Informational [Page 5] 283 | 284 | RFC 4226 HOTP Algorithm December 2005 285 | 286 | 287 | 288 | s resynchronization parameter: the server will attempt to 289 | verify a received authenticator across s consecutive 290 | counter values. 291 | 292 | Digit number of digits in an HOTP value; system parameter. 293 | 294 | 5.2. Description 295 | 296 | The HOTP algorithm is based on an increasing counter value and a 297 | static symmetric key known only to the token and the validation 298 | service. In order to create the HOTP value, we will use the HMAC- 299 | SHA-1 algorithm, as defined in RFC 2104 [BCK2]. 300 | 301 | As the output of the HMAC-SHA-1 calculation is 160 bits, we must 302 | truncate this value to something that can be easily entered by a 303 | user. 304 | 305 | HOTP(K,C) = Truncate(HMAC-SHA-1(K,C)) 306 | 307 | Where: 308 | 309 | - Truncate represents the function that converts an HMAC-SHA-1 310 | value into an HOTP value as defined in Section 5.3. 311 | 312 | The Key (K), the Counter (C), and Data values are hashed high-order 313 | byte first. 314 | 315 | The HOTP values generated by the HOTP generator are treated as big 316 | endian. 317 | 318 | 5.3. Generating an HOTP Value 319 | 320 | We can describe the operations in 3 distinct steps: 321 | 322 | Step 1: Generate an HMAC-SHA-1 value Let HS = HMAC-SHA-1(K,C) // HS 323 | is a 20-byte string 324 | 325 | Step 2: Generate a 4-byte string (Dynamic Truncation) 326 | Let Sbits = DT(HS) // DT, defined below, 327 | // returns a 31-bit string 328 | 329 | Step 3: Compute an HOTP value 330 | Let Snum = StToNum(Sbits) // Convert S to a number in 331 | 0...2^{31}-1 332 | Return D = Snum mod 10^Digit // D is a number in the range 333 | 0...10^{Digit}-1 334 | 335 | 336 | 337 | 338 | M'Raihi, et al. Informational [Page 6] 339 | 340 | RFC 4226 HOTP Algorithm December 2005 341 | 342 | 343 | The Truncate function performs Step 2 and Step 3, i.e., the dynamic 344 | truncation and then the reduction modulo 10^Digit. The purpose of 345 | the dynamic offset truncation technique is to extract a 4-byte 346 | dynamic binary code from a 160-bit (20-byte) HMAC-SHA-1 result. 347 | 348 | DT(String) // String = String[0]...String[19] 349 | Let OffsetBits be the low-order 4 bits of String[19] 350 | Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15 351 | Let P = String[OffSet]...String[OffSet+3] 352 | Return the Last 31 bits of P 353 | 354 | The reason for masking the most significant bit of P is to avoid 355 | confusion about signed vs. unsigned modulo computations. Different 356 | processors perform these operations differently, and masking out the 357 | signed bit removes all ambiguity. 358 | 359 | Implementations MUST extract a 6-digit code at a minimum and possibly 360 | 7 and 8-digit code. Depending on security requirements, Digit = 7 or 361 | more SHOULD be considered in order to extract a longer HOTP value. 362 | 363 | The following paragraph is an example of using this technique for 364 | Digit = 6, i.e., that a 6-digit HOTP value is calculated from the 365 | HMAC value. 366 | 367 | 5.4. Example of HOTP Computation for Digit = 6 368 | 369 | The following code example describes the extraction of a dynamic 370 | binary code given that hmac_result is a byte array with the HMAC- 371 | SHA-1 result: 372 | 373 | int offset = hmac_result[19] & 0xf ; 374 | int bin_code = (hmac_result[offset] & 0x7f) << 24 375 | | (hmac_result[offset+1] & 0xff) << 16 376 | | (hmac_result[offset+2] & 0xff) << 8 377 | | (hmac_result[offset+3] & 0xff) ; 378 | 379 | SHA-1 HMAC Bytes (Example) 380 | 381 | ------------------------------------------------------------- 382 | | Byte Number | 383 | ------------------------------------------------------------- 384 | |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19| 385 | ------------------------------------------------------------- 386 | | Byte Value | 387 | ------------------------------------------------------------- 388 | |1f|86|98|69|0e|02|ca|16|61|85|50|ef|7f|19|da|8e|94|5b|55|5a| 389 | -------------------------------***********----------------++| 390 | 391 | 392 | 393 | 394 | M'Raihi, et al. Informational [Page 7] 395 | 396 | RFC 4226 HOTP Algorithm December 2005 397 | 398 | 399 | * The last byte (byte 19) has the hex value 0x5a. 400 | * The value of the lower 4 bits is 0xa (the offset value). 401 | * The offset value is byte 10 (0xa). 402 | * The value of the 4 bytes starting at byte 10 is 0x50ef7f19, 403 | which is the dynamic binary code DBC1. 404 | * The MSB of DBC1 is 0x50 so DBC2 = DBC1 = 0x50ef7f19 . 405 | * HOTP = DBC2 modulo 10^6 = 872921. 406 | 407 | We treat the dynamic binary code as a 31-bit, unsigned, big-endian 408 | integer; the first byte is masked with a 0x7f. 409 | 410 | We then take this number modulo 1,000,000 (10^6) to generate the 6- 411 | digit HOTP value 872921 decimal. 412 | 413 | 6. Security Considerations 414 | 415 | The conclusion of the security analysis detailed in the Appendix is 416 | that, for all practical purposes, the outputs of the Dynamic 417 | Truncation (DT) on distinct counter inputs are uniformly and 418 | independently distributed 31-bit strings. 419 | 420 | The security analysis then details the impact of the conversion from 421 | a string to an integer and the final reduction modulo 10^Digit, where 422 | Digit is the number of digits in an HOTP value. 423 | 424 | The analysis demonstrates that these final steps introduce a 425 | negligible bias, which does not impact the security of the HOTP 426 | algorithm, in the sense that the best possible attack against the 427 | HOTP function is the brute force attack. 428 | 429 | Assuming an adversary is able to observe numerous protocol exchanges 430 | and collect sequences of successful authentication values. This 431 | adversary, trying to build a function F to generate HOTP values based 432 | on his observations, will not have a significant advantage over a 433 | random guess. 434 | 435 | The logical conclusion is simply that the best strategy will once 436 | again be to perform a brute force attack to enumerate and try all the 437 | possible values. 438 | 439 | Considering the security analysis in the Appendix of this document, 440 | without loss of generality, we can approximate closely the security 441 | of the HOTP algorithm by the following formula: 442 | 443 | Sec = sv/10^Digit 444 | 445 | 446 | 447 | 448 | 449 | 450 | M'Raihi, et al. Informational [Page 8] 451 | 452 | RFC 4226 HOTP Algorithm December 2005 453 | 454 | 455 | Where: 456 | - Sec is the probability of success of the adversary; 457 | - s is the look-ahead synchronization window size; 458 | - v is the number of verification attempts; 459 | - Digit is the number of digits in HOTP values. 460 | 461 | Obviously, we can play with s, T (the Throttling parameter that would 462 | limit the number of attempts by an attacker), and Digit until 463 | achieving a certain level of security, still preserving the system 464 | usability. 465 | 466 | 7. Security Requirements 467 | 468 | Any One-Time Password algorithm is only as secure as the application 469 | and the authentication protocols that implement it. Therefore, this 470 | section discusses the critical security requirements that our choice 471 | of algorithm imposes on the authentication protocol and validation 472 | software. 473 | 474 | The parameters T and s discussed in this section have a significant 475 | impact on the security -- further details in Section 6 elaborate on 476 | the relations between these parameters and their impact on the system 477 | security. 478 | 479 | It is also important to remark that the HOTP algorithm is not a 480 | substitute for encryption and does not provide for the privacy of 481 | data transmission. Other mechanisms should be used to defeat attacks 482 | aimed at breaking confidentiality and privacy of transactions. 483 | 484 | 7.1. Authentication Protocol Requirements 485 | 486 | We introduce in this section some requirements for a protocol P 487 | implementing HOTP as the authentication method between a prover and a 488 | verifier. 489 | 490 | RP1 - P MUST support two-factor authentication, i.e., the 491 | communication and verification of something you know (secret code 492 | such as a Password, Pass phrase, PIN code, etc.) and something you 493 | have (token). The secret code is known only to the user and usually 494 | entered with the One-Time Password value for authentication purpose 495 | (two-factor authentication). 496 | 497 | RP2 - P SHOULD NOT be vulnerable to brute force attacks. This 498 | implies that a throttling/lockout scheme is RECOMMENDED on the 499 | validation server side. 500 | 501 | RP3 - P SHOULD be implemented over a secure channel in order to 502 | protect users' privacy and avoid replay attacks. 503 | 504 | 505 | 506 | M'Raihi, et al. Informational [Page 9] 507 | 508 | RFC 4226 HOTP Algorithm December 2005 509 | 510 | 511 | 7.2. Validation of HOTP Values 512 | 513 | The HOTP client (hardware or software token) increments its counter 514 | and then calculates the next HOTP value HOTP client. If the value 515 | received by the authentication server matches the value calculated by 516 | the client, then the HOTP value is validated. In this case, the 517 | server increments the counter value by one. 518 | 519 | If the value received by the server does not match the value 520 | calculated by the client, the server initiate the resynch protocol 521 | (look-ahead window) before it requests another pass. 522 | 523 | If the resynch fails, the server asks then for another 524 | authentication pass of the protocol to take place, until the 525 | maximum number of authorized attempts is reached. 526 | 527 | If and when the maximum number of authorized attempts is reached, the 528 | server SHOULD lock out the account and initiate a procedure to inform 529 | the user. 530 | 531 | 7.3. Throttling at the Server 532 | 533 | Truncating the HMAC-SHA-1 value to a shorter value makes a brute 534 | force attack possible. Therefore, the authentication server needs to 535 | detect and stop brute force attacks. 536 | 537 | We RECOMMEND setting a throttling parameter T, which defines the 538 | maximum number of possible attempts for One-Time Password validation. 539 | The validation server manages individual counters per HOTP device in 540 | order to take note of any failed attempt. We RECOMMEND T not to be 541 | too large, particularly if the resynchronization method used on the 542 | server is window-based, and the window size is large. T SHOULD be 543 | set as low as possible, while still ensuring that usability is not 544 | significantly impacted. 545 | 546 | Another option would be to implement a delay scheme to avoid a brute 547 | force attack. After each failed attempt A, the authentication server 548 | would wait for an increased T*A number of seconds, e.g., say T = 5, 549 | then after 1 attempt, the server waits for 5 seconds, at the second 550 | failed attempt, it waits for 5*2 = 10 seconds, etc. 551 | 552 | The delay or lockout schemes MUST be across login sessions to prevent 553 | attacks based on multiple parallel guessing techniques. 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | M'Raihi, et al. Informational [Page 10] 563 | 564 | RFC 4226 HOTP Algorithm December 2005 565 | 566 | 567 | 7.4. Resynchronization of the Counter 568 | 569 | Although the server's counter value is only incremented after a 570 | successful HOTP authentication, the counter on the token is 571 | incremented every time a new HOTP is requested by the user. Because 572 | of this, the counter values on the server and on the token might be 573 | out of synchronization. 574 | 575 | We RECOMMEND setting a look-ahead parameter s on the server, which 576 | defines the size of the look-ahead window. In a nutshell, the server 577 | can recalculate the next s HOTP-server values, and check them against 578 | the received HOTP client. 579 | 580 | Synchronization of counters in this scenario simply requires the 581 | server to calculate the next HOTP values and determine if there is a 582 | match. Optionally, the system MAY require the user to send a 583 | sequence of (say, 2, 3) HOTP values for resynchronization purpose, 584 | since forging a sequence of consecutive HOTP values is even more 585 | difficult than guessing a single HOTP value. 586 | 587 | The upper bound set by the parameter s ensures the server does not go 588 | on checking HOTP values forever (causing a denial-of-service attack) 589 | and also restricts the space of possible solutions for an attacker 590 | trying to manufacture HOTP values. s SHOULD be set as low as 591 | possible, while still ensuring that usability is not impacted. 592 | 593 | 7.5. Management of Shared Secrets 594 | 595 | The operations dealing with the shared secrets used to generate and 596 | verify OTP values must be performed securely, in order to mitigate 597 | risks of any leakage of sensitive information. We describe in this 598 | section different modes of operations and techniques to perform these 599 | different operations with respect to the state of the art in data 600 | security. 601 | 602 | We can consider two different avenues for generating and storing 603 | (securely) shared secrets in the Validation system: 604 | 605 | * Deterministic Generation: secrets are derived from a master 606 | seed, both at provisioning and verification stages and generated 607 | on-the-fly whenever it is required. 608 | * Random Generation: secrets are generated randomly at 609 | provisioning stage and must be stored immediately and kept 610 | secure during their life cycle. 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | M'Raihi, et al. Informational [Page 11] 619 | 620 | RFC 4226 HOTP Algorithm December 2005 621 | 622 | 623 | Deterministic Generation 624 | ------------------------ 625 | 626 | A possible strategy is to derive the shared secrets from a master 627 | secret. The master secret will be stored at the server only. A 628 | tamper-resistant device MUST be used to store the master key and 629 | derive the shared secrets from the master key and some public 630 | information. The main benefit would be to avoid the exposure of the 631 | shared secrets at any time and also avoid specific requirements on 632 | storage, since the shared secrets could be generated on-demand when 633 | needed at provisioning and validation time. 634 | 635 | We distinguish two different cases: 636 | 637 | - A single master key MK is used to derive the shared secrets; 638 | each HOTP device has a different secret, K_i = SHA-1 (MK,i) 639 | where i stands for a public piece of information that identifies 640 | uniquely the HOTP device such as a serial number, a token ID, 641 | etc. Obviously, this is in the context of an application or 642 | service -- different application or service providers will have 643 | different secrets and settings. 644 | - Several master keys MK_i are used and each HOTP device stores a 645 | set of different derived secrets, {K_i,j = SHA-1(MK_i,j)} where 646 | j stands for a public piece of information identifying the 647 | device. The idea would be to store ONLY the active master key 648 | at the validation server, in the Hardware Security Module (HSM), 649 | and keep in a safe place, using secret sharing methods such as 650 | [Shamir] for instance. In this case, if a master secret MK_i is 651 | compromised, then it is possible to switch to another secret 652 | without replacing all the devices. 653 | 654 | The drawback in the deterministic case is that the exposure of the 655 | master secret would obviously enable an attacker to rebuild any 656 | shared secret based on correct public information. The revocation of 657 | all secrets would be required, or switching to a new set of secrets 658 | in the case of multiple master keys. 659 | 660 | On the other hand, the device used to store the master key(s) and 661 | generate the shared secrets MUST be tamper resistant. Furthermore, 662 | the HSM will not be exposed outside the security perimeter of the 663 | validation system, therefore reducing the risk of leakage. 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | M'Raihi, et al. Informational [Page 12] 675 | 676 | RFC 4226 HOTP Algorithm December 2005 677 | 678 | 679 | Random Generation 680 | ----------------- 681 | 682 | The shared secrets are randomly generated. We RECOMMEND following 683 | the recommendations in [RFC4086] and selecting a good and secure 684 | random source for generating these secrets. A (true) random 685 | generator requires a naturally occurring source of randomness. 686 | Practically, there are two possible avenues to consider for the 687 | generation of the shared secrets: 688 | 689 | * Hardware-based generators: they exploit the randomness that 690 | occurs in physical phenomena. A nice implementation can be based on 691 | oscillators and built in such ways that active attacks are more 692 | difficult to perform. 693 | 694 | * Software-based generators: designing a good software random 695 | generator is not an easy task. A simple, but efficient, 696 | implementation should be based on various sources and apply to the 697 | sampled sequence a one-way function such as SHA-1. 698 | 699 | We RECOMMEND selecting proven products, being hardware or software 700 | generators, for the computation of shared secrets. 701 | 702 | We also RECOMMEND storing the shared secrets securely, and more 703 | specifically encrypting the shared secrets when stored using tamper- 704 | resistant hardware encryption and exposing them only when required: 705 | for example, the shared secret is decrypted when needed to verify an 706 | HOTP value, and re-encrypted immediately to limit exposure in the RAM 707 | for a short period of time. The data store holding the shared 708 | secrets MUST be in a secure area, to avoid as much as possible direct 709 | attack on the validation system and secrets database. 710 | 711 | Particularly, access to the shared secrets should be limited to 712 | programs and processes required by the validation system only. We 713 | will not elaborate on the different security mechanisms to put in 714 | place, but obviously, the protection of shared secrets is of the 715 | uttermost importance. 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | M'Raihi, et al. Informational [Page 13] 731 | 732 | RFC 4226 HOTP Algorithm December 2005 733 | 734 | 735 | 8. Composite Shared Secrets 736 | 737 | It may be desirable to include additional authentication factors in 738 | the shared secret K. These additional factors can consist of any 739 | data known at the token but not easily obtained by others. Examples 740 | of such data include: 741 | 742 | * PIN or Password obtained as user input at the token 743 | * Phone number 744 | * Any unique identifier programmatically available at the token 745 | 746 | In this scenario, the composite shared secret K is constructed during 747 | the provisioning process from a random seed value combined with one 748 | or more additional authentication factors. The server could either 749 | build on-demand or store composite secrets -- in any case, depending 750 | on implementation choice, the token only stores the seed value. When 751 | the token performs the HOTP calculation, it computes K from the seed 752 | value and the locally derived or input values of the other 753 | authentication factors. 754 | 755 | The use of composite shared secrets can strengthen HOTP-based 756 | authentication systems through the inclusion of additional 757 | authentication factors at the token. To the extent that the token is 758 | a trusted device, this approach has the further benefit of not 759 | requiring exposure of the authentication factors (such as the user 760 | input PIN) to other devices. 761 | 762 | 9. Bi-Directional Authentication 763 | 764 | Interestingly enough, the HOTP client could also be used to 765 | authenticate the validation server, claiming that it is a genuine 766 | entity knowing the shared secret. 767 | 768 | Since the HOTP client and the server are synchronized and share the 769 | same secret (or a method to recompute it), a simple 3-pass protocol 770 | could be put in place: 771 | 1- The end user enter the TokenID and a first OTP value OTP1; 772 | 2- The server checks OTP1 and if correct, sends back OTP2; 773 | 3- The end user checks OTP2 using his HOTP device and if correct, 774 | uses the web site. 775 | 776 | Obviously, as indicated previously, all the OTP communications have 777 | to take place over a secure channel, e.g., SSL/TLS, IPsec 778 | connections. 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | M'Raihi, et al. Informational [Page 14] 787 | 788 | RFC 4226 HOTP Algorithm December 2005 789 | 790 | 791 | 10. Conclusion 792 | 793 | This document describes HOTP, a HMAC-based One-Time Password 794 | algorithm. It also recommends the preferred implementation and 795 | related modes of operations for deploying the algorithm. 796 | 797 | The document also exhibits elements of security and demonstrates that 798 | the HOTP algorithm is practical and sound, the best possible attack 799 | being a brute force attack that can be prevented by careful 800 | implementation of countermeasures in the validation server. 801 | 802 | Eventually, several enhancements have been proposed, in order to 803 | improve security if needed for specific applications. 804 | 805 | 11. Acknowledgements 806 | 807 | The authors would like to thank Siddharth Bajaj, Alex Deacon, Loren 808 | Hart, and Nico Popp for their help during the conception and 809 | redaction of this document. 810 | 811 | 12. Contributors 812 | 813 | The authors of this document would like to emphasize the role of 814 | three persons who have made a key contribution to this document: 815 | 816 | - Laszlo Elteto is system architect with SafeNet, Inc. 817 | 818 | - Ernesto Frutos is director of Engineering with Authenex, Inc. 819 | 820 | - Fred McClain is Founder and CTO with Boojum Mobile, Inc. 821 | 822 | Without their advice and valuable inputs, this document would not be 823 | the same. 824 | 825 | 13. References 826 | 827 | 13.1. Normative References 828 | 829 | [BCK1] M. Bellare, R. Canetti and H. Krawczyk, "Keyed Hash 830 | Functions and Message Authentication", Proceedings of 831 | Crypto'96, LNCS Vol. 1109, pp. 1-15. 832 | 833 | [BCK2] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed- 834 | Hashing for Message Authentication", RFC 2104, February 835 | 1997. 836 | 837 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate 838 | Requirement Levels", BCP 14, RFC 2119, March 1997. 839 | 840 | 841 | 842 | M'Raihi, et al. Informational [Page 15] 843 | 844 | RFC 4226 HOTP Algorithm December 2005 845 | 846 | 847 | [RFC3979] Bradner, S., "Intellectual Property Rights in IETF 848 | Technology", BCP 79, RFC 3979, March 2005. 849 | 850 | [RFC4086] Eastlake, D., 3rd, Schiller, J., and S. Crocker, 851 | "Randomness Requirements for Security", BCP 106, RFC 4086, 852 | June 2005. 853 | 854 | 13.2. Informative References 855 | 856 | [OATH] Initiative for Open AuTHentication 857 | http://www.openauthentication.org 858 | 859 | [PrOo] B. Preneel and P. van Oorschot, "MD-x MAC and building 860 | fast MACs from hash functions", Advances in Cryptology 861 | CRYPTO '95, Lecture Notes in Computer Science Vol. 963, D. 862 | Coppersmith ed., Springer-Verlag, 1995. 863 | 864 | [Crack] Crack in SHA-1 code 'stuns' security gurus 865 | http://www.eetimes.com/showArticle.jhtml? 866 | articleID=60402150 867 | 868 | [Sha1] Bruce Schneier. SHA-1 broken. February 15, 2005. 869 | http://www.schneier.com/blog/archives/2005/02/ 870 | sha1_broken.html 871 | 872 | [Res] Researchers: Digital encryption standard flawed 873 | http://news.com.com/ 874 | Researchers+Digital+encryption+standard+flawed/ 875 | 2100-1002-5579881.html?part=dht&tag=ntop&tag=nl.e703 876 | 877 | [Shamir] How to Share a Secret, by Adi Shamir. In Communications 878 | of the ACM, Vol. 22, No. 11, pp. 612-613, November, 1979. 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | M'Raihi, et al. Informational [Page 16] 899 | 900 | RFC 4226 HOTP Algorithm December 2005 901 | 902 | 903 | Appendix A - HOTP Algorithm Security: Detailed Analysis 904 | 905 | The security analysis of the HOTP algorithm is summarized in this 906 | section. We first detail the best attack strategies, and then 907 | elaborate on the security under various assumptions and the impact of 908 | the truncation and make some recommendations regarding the number of 909 | digits. 910 | 911 | We focus this analysis on the case where Digit = 6, i.e., an HOTP 912 | function that produces 6-digit values, which is the bare minimum 913 | recommended in this document. 914 | 915 | A.1. Definitions and Notations 916 | 917 | We denote by {0,1}^l the set of all strings of length l. 918 | 919 | Let Z_{n} = {0,.., n - 1}. 920 | 921 | Let IntDiv(a,b) denote the integer division algorithm that takes 922 | input integers a, b where a >= b >= 1 and returns integers (q,r) 923 | 924 | the quotient and remainder, respectively, of the division of a by b. 925 | (Thus, a = bq + r and 0 <= r < b.) 926 | 927 | Let H: {0,1}^k x {0,1}^c --> {0,1}^n be the base function that takes 928 | a k-bit key K and c-bit counter C and returns an n-bit output H(K,C). 929 | (In the case of HOTP, H is HMAC-SHA-1; we use this formal definition 930 | for generalizing our proof of security.) 931 | 932 | A.2. The Idealized Algorithm: HOTP-IDEAL 933 | 934 | We now define an idealized counterpart of the HOTP algorithm. In 935 | this algorithm, the role of H is played by a random function that 936 | forms the key. 937 | 938 | To be more precise, let Maps(c,n) denote the set of all functions 939 | mapping from {0,1}^c to {0,1}^n. The idealized algorithm has key 940 | space Maps(c,n), so that a "key" for such an algorithm is a function 941 | h from {0,1}^c to {0,1}^n. We imagine this key (function) to be 942 | drawn at random. It is not feasible to implement this idealized 943 | algorithm, since the key, being a function from {0,1}^c to {0,1}^n, 944 | is way too large to even store. So why consider it? 945 | 946 | Our security analysis will show that as long as H satisfies a certain 947 | well-accepted assumption, the security of the actual and idealized 948 | algorithms is for all practical purposes the same. The task that 949 | really faces us, then, is to assess the security of the idealized 950 | algorithm. 951 | 952 | 953 | 954 | M'Raihi, et al. Informational [Page 17] 955 | 956 | RFC 4226 HOTP Algorithm December 2005 957 | 958 | 959 | In analyzing the idealized algorithm, we are concentrating on 960 | assessing the quality of the design of the algorithm itself, 961 | independently of HMAC-SHA-1. This is in fact the important issue. 962 | 963 | A.3. Model of Security 964 | 965 | The model exhibits the type of threats or attacks that are being 966 | considered and enables one to assess the security of HOTP and HOTP- 967 | IDEAL. We denote ALG as either HOTP or HOTP-IDEAL for the purpose of 968 | this security analysis. 969 | 970 | The scenario we are considering is that a user and server share a key 971 | K for ALG. Both maintain a counter C, initially zero, and the user 972 | authenticates itself by sending ALG(K,C) to the server. The latter 973 | accepts if this value is correct. 974 | 975 | In order to protect against accidental increment of the user counter, 976 | the server, upon receiving a value z, will accept as long as z equals 977 | ALG(K,i) for some i in the range C,...,C + s-1, where s is the 978 | resynchronization parameter and C is the server counter. If it 979 | accepts with some value of i, it then increments its counter to i+1. 980 | If it does not accept, it does not change its counter value. 981 | 982 | The model we specify captures what an adversary can do and what it 983 | needs to achieve in order to "win". First, the adversary is assumed 984 | to be able to eavesdrop, meaning, to see the authenticator 985 | transmitted by the user. Second, the adversary wins if it can get 986 | the server to accept an authenticator relative to a counter value for 987 | which the user has never transmitted an authenticator. 988 | 989 | The formal adversary, which we denote by B, starts out knowing which 990 | algorithm ALG is being used, knowing the system design, and knowing 991 | all system parameters. The one and only thing it is not given a 992 | priori is the key K shared between the user and the server. 993 | 994 | The model gives B full control of the scheduling of events. It has 995 | access to an authenticator oracle representing the user. By calling 996 | this oracle, the adversary can ask the user to authenticate itself 997 | and get back the authenticator in return. It can call this oracle as 998 | often as it wants and when it wants, using the authenticators it 999 | accumulates to perhaps "learn" how to make authenticators itself. At 1000 | any time, it may also call a verification oracle, supplying the 1001 | latter with a candidate authenticator of its choice. It wins if the 1002 | server accepts this accumulator. 1003 | 1004 | Consider the following game involving an adversary B that is 1005 | attempting to compromise the security of an authentication algorithm 1006 | ALG: K x {0,1}^c --> R. 1007 | 1008 | 1009 | 1010 | M'Raihi, et al. Informational [Page 18] 1011 | 1012 | RFC 4226 HOTP Algorithm December 2005 1013 | 1014 | 1015 | Initializations - A key K is selected at random from K, a counter C 1016 | is initialized to 0, and the Boolean value win is set to false. 1017 | 1018 | Game execution - Adversary B is provided with the two following 1019 | oracles: 1020 | 1021 | Oracle AuthO() 1022 | -------------- 1023 | A = ALG(K,C) 1024 | C = C + 1 1025 | Return O to B 1026 | 1027 | Oracle VerO(A) 1028 | -------------- 1029 | i = C 1030 | While (i <= C + s - 1 and Win == FALSE) do 1031 | If A == ALG(K,i) then Win = TRUE; C = i + 1 1032 | Else i = i + 1 1033 | Return Win to B 1034 | 1035 | AuthO() is the authenticator oracle and VerO(A) is the verification 1036 | oracle. 1037 | 1038 | Upon execution, B queries the two oracles at will. Let Adv(B) be the 1039 | probability that win gets set to true in the above game. This is the 1040 | probability that the adversary successfully impersonates the user. 1041 | 1042 | Our goal is to assess how large this value can be as a function of 1043 | the number v of verification queries made by B, the number a of 1044 | authenticator oracle queries made by B, and the running time t of B. 1045 | This will tell us how to set the throttle, which effectively upper 1046 | bounds v. 1047 | 1048 | A.4. Security of the Ideal Authentication Algorithm 1049 | 1050 | This section summarizes the security analysis of HOTP-IDEAL, starting 1051 | with the impact of the conversion modulo 10^Digit and then focusing 1052 | on the different possible attacks. 1053 | 1054 | A.4.1. From Bits to Digits 1055 | 1056 | The dynamic offset truncation of a random n-bit string yields a 1057 | random 31-bit string. What happens to the distribution when it is 1058 | taken modulo m = 10^Digit, as done in HOTP? 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | M'Raihi, et al. Informational [Page 19] 1067 | 1068 | RFC 4226 HOTP Algorithm December 2005 1069 | 1070 | 1071 | The following lemma estimates the biases in the outputs in this case. 1072 | 1073 | Lemma 1 1074 | ------- 1075 | Let N >= m >= 1 be integers, and let (q,r) = IntDiv(N,m). For z in 1076 | Z_{m} let: 1077 | 1078 | P_{N,m}(z) = Pr [x mod m = z : x randomly pick in Z_{n}] 1079 | 1080 | Then for any z in Z_{m} 1081 | 1082 | P_{N,m}(z) = (q + 1) / N if 0 <= z < r 1083 | q / N if r <= z < m 1084 | 1085 | Proof of Lemma 1 1086 | ---------------- 1087 | Let the random variable X be uniformly distributed over Z_{N}. Then: 1088 | 1089 | P_{N,m}(z) = Pr [X mod m = z] 1090 | 1091 | = Pr [X < mq] * Pr [X mod m = z| X < mq] 1092 | + Pr [mq <= X < N] * Pr [X mod m = z| mq <= X < N] 1093 | 1094 | = mq/N * 1/m + 1095 | (N - mq)/N * 1 / (N - mq) if 0 <= z < N - mq 1096 | 0 if N - mq <= z <= m 1097 | 1098 | = q/N + 1099 | r/N * 1 / r if 0 <= z < N - mq 1100 | 0 if r <= z <= m 1101 | 1102 | Simplifying yields the claimed equation. 1103 | 1104 | Let N = 2^31, d = 6, and m = 10^d. If x is chosen at random from 1105 | Z_{N} (meaning, is a random 31-bit string), then reducing it to a 6- 1106 | digit number by taking x mod m does not yield a random 6-digit 1107 | number. 1108 | 1109 | Rather, x mod m is distributed as shown in the following table: 1110 | 1111 | Values Probability that each appears as output 1112 | ---------------------------------------------------------------- 1113 | 0,1,...,483647 2148/2^31 roughly equals to 1.00024045/10^6 1114 | 483648,...,999999 2147/2^31 roughly equals to 0.99977478/10^6 1115 | 1116 | If X is uniformly distributed over Z_{2^31} (meaning, is a random 1117 | 31-bit string), then the above shows the probabilities for different 1118 | outputs of X mod 10^6. The first set of values appears with 1119 | 1120 | 1121 | 1122 | M'Raihi, et al. Informational [Page 20] 1123 | 1124 | RFC 4226 HOTP Algorithm December 2005 1125 | 1126 | 1127 | probability slightly greater than 10^-6, the rest with probability 1128 | slightly less, meaning that the distribution is slightly non-uniform. 1129 | 1130 | However, as the table above indicates, the bias is small, and as we 1131 | will see later, negligible: the probabilities are very close to 1132 | 10^-6. 1133 | 1134 | A.4.2. Brute Force Attacks 1135 | 1136 | If the authenticator consisted of d random digits, then a brute force 1137 | attack using v verification attempts would succeed with probability 1138 | sv/10^Digit. 1139 | 1140 | However, an adversary can exploit the bias in the outputs of 1141 | HOTP-IDEAL, predicted by Lemma 1, to mount a slightly better attack. 1142 | 1143 | Namely, it makes authentication attempts with authenticators that are 1144 | the most likely values, meaning the ones in the range 0,...,r - 1, 1145 | where (q,r) = IntDiv(2^31,10^Digit). 1146 | 1147 | The following specifies an adversary in our model of security that 1148 | mounts the attack. It estimates the success probability as a 1149 | function of the number of verification queries. 1150 | 1151 | For simplicity, we assume that the number of verification queries is 1152 | at most r. With N = 2^31 and m = 10^6, we have r = 483,648, and the 1153 | throttle value is certainly less than this, so this assumption is not 1154 | much of a restriction. 1155 | 1156 | Proposition 1 1157 | ------------- 1158 | 1159 | Suppose m = 10^Digit < 2^31, and let (q,r) = IntDiv(2^31,m). Assume 1160 | s <= m. The brute-force-attack adversary B-bf attacks HOTP using v 1161 | <= r verification oracle queries. This adversary makes no 1162 | authenticator oracle queries, and succeeds with probability 1163 | 1164 | Adv(B-bf) = 1 - (1 - v(q+1)/2^31)^s 1165 | 1166 | which is roughly equal to 1167 | 1168 | sv * (q+1)/2^31 1169 | 1170 | With m = 10^6 we get q = 2,147. In that case, the brute force attack 1171 | using v verification attempts succeeds with probability 1172 | 1173 | Adv(B-bf) roughly = sv * 2148/2^31 = sv * 1.00024045/10^6 1174 | 1175 | 1176 | 1177 | 1178 | M'Raihi, et al. Informational [Page 21] 1179 | 1180 | RFC 4226 HOTP Algorithm December 2005 1181 | 1182 | 1183 | As this equation shows, the resynchronization parameter s has a 1184 | significant impact in that the adversary's success probability is 1185 | proportional to s. This means that s cannot be made too large 1186 | without compromising security. 1187 | 1188 | A.4.3. Brute force attacks are the best possible attacks. 1189 | 1190 | A central question is whether there are attacks any better than the 1191 | brute force one. In particular, the brute force attack did not 1192 | attempt to collect authenticators sent by the user and try to 1193 | cryptanalyze them in an attempt to learn how to better construct 1194 | authenticators. Would doing this help? Is there some way to "learn" 1195 | how to build authenticators that result in a higher success rate than 1196 | given by the brute-force attack? 1197 | 1198 | The following says the answer to these questions is no. No matter 1199 | what strategy the adversary uses, and even if it sees, and tries to 1200 | exploit, the authenticators from authentication attempts of the user, 1201 | its success probability will not be above that of the brute force 1202 | attack -- this is true as long as the number of authentications it 1203 | observes is not incredibly large. This is valuable information 1204 | regarding the security of the scheme. 1205 | 1206 | Proposition 2 ------------- Suppose m = 10^Digit < 2^31, and let 1207 | (q,r) = IntDiv(2^31,m). Let B be any adversary attacking HOTP-IDEAL 1208 | using v verification oracle queries and a <= 2^c - s authenticator 1209 | oracle queries. Then 1210 | 1211 | Adv(B) < = sv * (q+1)/ 2^31 1212 | 1213 | Note: This result is conditional on the adversary not seeing more 1214 | than 2^c - s authentications performed by the user, which is hardly 1215 | restrictive as long as c is large enough. 1216 | 1217 | With m = 10^6, we get q = 2,147. In that case, Proposition 2 says 1218 | that any adversary B attacking HOTP-IDEAL and making v verification 1219 | attempts succeeds with probability at most 1220 | 1221 | Equation 1 1222 | ---------- 1223 | sv * 2148/2^31 roughly = sv * 1.00024045/10^6 1224 | 1225 | Meaning, B's success rate is not more than that achieved by the brute 1226 | force attack. 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | M'Raihi, et al. Informational [Page 22] 1235 | 1236 | RFC 4226 HOTP Algorithm December 2005 1237 | 1238 | 1239 | A.5. Security Analysis of HOTP 1240 | 1241 | We have analyzed, in the previous sections, the security of the 1242 | idealized counterparts HOTP-IDEAL of the actual authentication 1243 | algorithm HOTP. We now show that, under appropriate and well- 1244 | believed assumption on H, the security of the actual algorithms is 1245 | essentially the same as that of its idealized counterpart. 1246 | 1247 | The assumption in question is that H is a secure pseudorandom 1248 | function, or PRF, meaning that its input-output values are 1249 | indistinguishable from those of a random function in practice. 1250 | 1251 | Consider an adversary A that is given an oracle for a function f: 1252 | {0,1}^c --> {0, 1}^n and eventually outputs a bit. We denote Adv(A) 1253 | as the prf-advantage of A, which represents how well the adversary 1254 | does at distinguishing the case where its oracle is H(K,.) from the 1255 | case where its oracle is a random function of {0,1}^c to {0,1}^n. 1256 | 1257 | One possible attack is based on exhaustive search for the key K. If 1258 | A runs for t steps and T denotes the time to perform one computation 1259 | of H, its prf-advantage from this attack turns out to be (t/T)2^-k. 1260 | Another possible attack is a birthday one [PrOo], whereby A can 1261 | attain advantage p^2/2^n in p oracle queries and running time about 1262 | pT. 1263 | 1264 | Our assumption is that these are the best possible attacks. This 1265 | translates into the following. 1266 | 1267 | Assumption 1 1268 | ------------ 1269 | 1270 | Let T denotes the time to perform one computation of H. Then if A is 1271 | any adversary with running time at most t and making at most p oracle 1272 | queries, 1273 | 1274 | Adv(A) <= (t/T)/2^k + p^2/2^n 1275 | 1276 | In practice, this assumption means that H is very secure as PRF. For 1277 | example, given that k = n = 160, an attacker with running time 2^60 1278 | and making 2^40 oracle queries has advantage at most (about) 2^-80. 1279 | 1280 | Theorem 1 1281 | --------- 1282 | 1283 | Suppose m = 10^Digit < 2^31, and let (q,r) = IntDiv(2^31,m). Let B 1284 | be any adversary attacking HOTP using v verification oracle queries, 1285 | 1286 | 1287 | 1288 | 1289 | 1290 | M'Raihi, et al. Informational [Page 23] 1291 | 1292 | RFC 4226 HOTP Algorithm December 2005 1293 | 1294 | 1295 | a <= 2^c - s authenticator oracle queries, and running time t. Let T 1296 | denote the time to perform one computation of H. If Assumption 1 is 1297 | true, then 1298 | 1299 | Adv(B) <= sv * (q + 1)/2^31 + (t/T)/2^k + ((sv + a)^2)/2^n 1300 | 1301 | In practice, the (t/T)2^-k + ((sv + a)^2)2^-n term is much smaller 1302 | than the sv(q + 1)/2^n term, so that the above says that for all 1303 | practical purposes the success rate of an adversary attacking HOTP is 1304 | sv(q + 1)/2^n, just as for HOTP-IDEAL, meaning the HOTP algorithm is 1305 | in practice essentially as good as its idealized counterpart. 1306 | 1307 | In the case m = 10^6 of a 6-digit output, this means that an 1308 | adversary making v authentication attempts will have a success rate 1309 | that is at most that of Equation 1. 1310 | 1311 | For example, consider an adversary with running time at most 2^60 1312 | that sees at most 2^40 authentication attempts of the user. Both 1313 | these choices are very generous to the adversary, who will typically 1314 | not have these resources, but we are saying that even such a powerful 1315 | adversary will not have more success than indicated by Equation 1. 1316 | 1317 | We can safely assume sv <= 2^40 due to the throttling and bounds on 1318 | s. So: 1319 | 1320 | (t/T)/2^k + ((sv + a)^2)/2^n <= 2^60/2^160 + (2^41)^2/2^160 1321 | roughly <= 2^-78 1322 | 1323 | which is much smaller than the success probability of Equation 1 and 1324 | negligible compared to it. 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | M'Raihi, et al. Informational [Page 24] 1347 | 1348 | RFC 4226 HOTP Algorithm December 2005 1349 | 1350 | 1351 | Appendix B - SHA-1 Attacks 1352 | 1353 | This sections addresses the impact of the recent attacks on SHA-1 on 1354 | the security of the HMAC-SHA-1-based HOTP. We begin with some 1355 | discussion of the situation of SHA-1 and then discuss the relevance 1356 | to HMAC-SHA-1 and HOTP. Cited references are in Section 13. 1357 | 1358 | B.1. SHA-1 Status 1359 | 1360 | A collision for a hash function h means a pair x,y of different 1361 | inputs such that h(x)=h(y). Since SHA-1 outputs 160 bits, a birthday 1362 | attack finds a collision in 2^{80} trials. (A trial means one 1363 | computation of the function.) This was thought to be the best 1364 | possible until Wang, Yin, and Yu announced on February 15, 2005, that 1365 | they had an attack finding collisions in 2^{69} trials. 1366 | 1367 | Is SHA-1 broken? For most practical purposes, we would say probably 1368 | not, since the resources needed to mount the attack are huge. Here 1369 | is one way to get a sense of it: we can estimate it is about the same 1370 | as the time we would need to factor a 760-bit RSA modulus, and this 1371 | is currently considered out of reach. 1372 | 1373 | Burr of NIST is quoted in [Crack] as saying "Large national 1374 | intelligence agencies could do this in a reasonable amount of time 1375 | with a few million dollars in computer time". However, the 1376 | computation may be out of reach of all but such well-funded agencies. 1377 | 1378 | One should also ask what impact finding SHA-1 collisions actually has 1379 | on security of real applications such as signatures. To exploit a 1380 | collision x,y to forge signatures, you need to somehow obtain a 1381 | signature of x and then you can forge a signature of y. How damaging 1382 | this is depends on the content of y: the y created by the attack may 1383 | not be meaningful in the application context. Also, one needs a 1384 | chosen-message attack to get the signature of x. This seems possible 1385 | in some contexts, but not others. Overall, it is not clear that the 1386 | impact on the security of signatures is significant. 1387 | 1388 | Indeed, one can read in the press that SHA-1 is "broken" [Sha1] and 1389 | that encryption and SSL are "broken" [Res]. The media have a 1390 | tendency to magnify events: it would hardly be interesting to 1391 | announce in the news that a team of cryptanalysts did very 1392 | interesting theoretical work in attacking SHA-1. 1393 | 1394 | Cryptographers are excited too. But mainly because this is an 1395 | important theoretical breakthrough. Attacks can only get better with 1396 | time: it is therefore important to monitor any progress in hash 1397 | functions cryptanalysis and be prepared for any really practical 1398 | break with a sound migration plan for the future. 1399 | 1400 | 1401 | 1402 | M'Raihi, et al. Informational [Page 25] 1403 | 1404 | RFC 4226 HOTP Algorithm December 2005 1405 | 1406 | 1407 | B.2. HMAC-SHA-1 Status 1408 | 1409 | The new attacks on SHA-1 have no impact on the security of 1410 | HMAC-SHA-1. The best attack on the latter remains one needing a 1411 | sender to authenticate 2^{80} messages before an adversary can create 1412 | a forgery. Why? 1413 | 1414 | HMAC is not a hash function. It is a message authentication code 1415 | (MAC) that uses a hash function internally. A MAC depends on a 1416 | secret key, while hash functions don't. What one needs to worry 1417 | about with a MAC is forgery, not collisions. HMAC was designed so 1418 | that collisions in the hash function (here SHA-1) do not yield 1419 | forgeries for HMAC. 1420 | 1421 | Recall that HMAC-SHA-1(K,x) = SHA-1(K_o,SHA-1(K_i,x)) where the keys 1422 | K_o,K_i are derived from K. Suppose the attacker finds a pair x,y 1423 | such that SHA-1(K_i,x) = SHA-1(K_i,y). (Call this a hidden-key 1424 | collision.) Then if it can obtain the MAC of x (itself a tall 1425 | order), it can forge the MAC of y. (These values are the same.) But 1426 | finding hidden-key collisions is harder than finding collisions, 1427 | because the attacker does not know the hidden key K_i. All it may 1428 | have is some outputs of HMAC-SHA-1 with key K. To date, there are no 1429 | claims or evidence that the recent attacks on SHA-1 extend to find 1430 | hidden-key collisions. 1431 | 1432 | Historically, the HMAC design has already proven itself in this 1433 | regard. MD5 is considered broken in that collisions in this hash 1434 | function can be found relatively easily. But there is still no 1435 | attack on HMAC-MD5 better than the trivial 2^{64} time birthday one. 1436 | (MD5 outputs 128 bits, not 160.) We are seeing this strength of HMAC 1437 | coming into play again in the SHA-1 context. 1438 | 1439 | B.3. HOTP Status 1440 | 1441 | Since no new weakness has surfaced in HMAC-SHA-1, there is no impact 1442 | on HOTP. The best attacks on HOTP remain those described in the 1443 | document, namely, to try to guess output values. 1444 | 1445 | The security proof of HOTP requires that HMAC-SHA-1 behave like a 1446 | pseudorandom function. The quality of HMAC-SHA-1 as a pseudorandom 1447 | function is not impacted by the new attacks on SHA-1, and so neither 1448 | is this proven guarantee. 1449 | 1450 | 1451 | 1452 | 1453 | 1454 | 1455 | 1456 | 1457 | 1458 | M'Raihi, et al. Informational [Page 26] 1459 | 1460 | RFC 4226 HOTP Algorithm December 2005 1461 | 1462 | 1463 | Appendix C - HOTP Algorithm: Reference Implementation 1464 | 1465 | /* 1466 | * OneTimePasswordAlgorithm.java 1467 | * OATH Initiative, 1468 | * HOTP one-time password algorithm 1469 | * 1470 | */ 1471 | 1472 | /* Copyright (C) 2004, OATH. All rights reserved. 1473 | * 1474 | * License to copy and use this software is granted provided that it 1475 | * is identified as the "OATH HOTP Algorithm" in all material 1476 | * mentioning or referencing this software or this function. 1477 | * 1478 | * License is also granted to make and use derivative works provided 1479 | * that such works are identified as 1480 | * "derived from OATH HOTP algorithm" 1481 | * in all material mentioning or referencing the derived work. 1482 | * 1483 | * OATH (Open AuTHentication) and its members make no 1484 | * representations concerning either the merchantability of this 1485 | * software or the suitability of this software for any particular 1486 | * purpose. 1487 | * 1488 | * It is provided "as is" without express or implied warranty 1489 | * of any kind and OATH AND ITS MEMBERS EXPRESSaLY DISCLAIMS 1490 | * ANY WARRANTY OR LIABILITY OF ANY KIND relating to this software. 1491 | * 1492 | * These notices must be retained in any copies of any part of this 1493 | * documentation and/or software. 1494 | */ 1495 | 1496 | package org.openauthentication.otp; 1497 | 1498 | import java.io.IOException; 1499 | import java.io.File; 1500 | import java.io.DataInputStream; 1501 | import java.io.FileInputStream ; 1502 | import java.lang.reflect.UndeclaredThrowableException; 1503 | 1504 | import java.security.GeneralSecurityException; 1505 | import java.security.NoSuchAlgorithmException; 1506 | import java.security.InvalidKeyException; 1507 | 1508 | import javax.crypto.Mac; 1509 | import javax.crypto.spec.SecretKeySpec; 1510 | 1511 | 1512 | 1513 | 1514 | M'Raihi, et al. Informational [Page 27] 1515 | 1516 | RFC 4226 HOTP Algorithm December 2005 1517 | 1518 | 1519 | /** 1520 | * This class contains static methods that are used to calculate the 1521 | * One-Time Password (OTP) using 1522 | * JCE to provide the HMAC-SHA-1. 1523 | * 1524 | * @author Loren Hart 1525 | * @version 1.0 1526 | */ 1527 | public class OneTimePasswordAlgorithm { 1528 | private OneTimePasswordAlgorithm() {} 1529 | 1530 | // These are used to calculate the check-sum digits. 1531 | // 0 1 2 3 4 5 6 7 8 9 1532 | private static final int[] doubleDigits = 1533 | { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 }; 1534 | 1535 | /** 1536 | * Calculates the checksum using the credit card algorithm. 1537 | * This algorithm has the advantage that it detects any single 1538 | * mistyped digit and any single transposition of 1539 | * adjacent digits. 1540 | * 1541 | * @param num the number to calculate the checksum for 1542 | * @param digits number of significant places in the number 1543 | * 1544 | * @return the checksum of num 1545 | */ 1546 | public static int calcChecksum(long num, int digits) { 1547 | boolean doubleDigit = true; 1548 | int total = 0; 1549 | while (0 < digits--) { 1550 | int digit = (int) (num % 10); 1551 | num /= 10; 1552 | if (doubleDigit) { 1553 | digit = doubleDigits[digit]; 1554 | } 1555 | total += digit; 1556 | doubleDigit = !doubleDigit; 1557 | } 1558 | int result = total % 10; 1559 | if (result > 0) { 1560 | result = 10 - result; 1561 | } 1562 | return result; 1563 | } 1564 | 1565 | /** 1566 | * This method uses the JCE to provide the HMAC-SHA-1 1567 | 1568 | 1569 | 1570 | M'Raihi, et al. Informational [Page 28] 1571 | 1572 | RFC 4226 HOTP Algorithm December 2005 1573 | 1574 | 1575 | * algorithm. 1576 | * HMAC computes a Hashed Message Authentication Code and 1577 | * in this case SHA1 is the hash algorithm used. 1578 | * 1579 | * @param keyBytes the bytes to use for the HMAC-SHA-1 key 1580 | * @param text the message or text to be authenticated. 1581 | * 1582 | * @throws NoSuchAlgorithmException if no provider makes 1583 | * either HmacSHA1 or HMAC-SHA-1 1584 | * digest algorithms available. 1585 | * @throws InvalidKeyException 1586 | * The secret provided was not a valid HMAC-SHA-1 key. 1587 | * 1588 | */ 1589 | 1590 | public static byte[] hmac_sha1(byte[] keyBytes, byte[] text) 1591 | throws NoSuchAlgorithmException, InvalidKeyException 1592 | { 1593 | // try { 1594 | Mac hmacSha1; 1595 | try { 1596 | hmacSha1 = Mac.getInstance("HmacSHA1"); 1597 | } catch (NoSuchAlgorithmException nsae) { 1598 | hmacSha1 = Mac.getInstance("HMAC-SHA-1"); 1599 | } 1600 | SecretKeySpec macKey = 1601 | new SecretKeySpec(keyBytes, "RAW"); 1602 | hmacSha1.init(macKey); 1603 | return hmacSha1.doFinal(text); 1604 | // } catch (GeneralSecurityException gse) { 1605 | // throw new UndeclaredThrowableException(gse); 1606 | // } 1607 | } 1608 | 1609 | private static final int[] DIGITS_POWER 1610 | // 0 1 2 3 4 5 6 7 8 1611 | = {1,10,100,1000,10000,100000,1000000,10000000,100000000}; 1612 | 1613 | /** 1614 | * This method generates an OTP value for the given 1615 | * set of parameters. 1616 | * 1617 | * @param secret the shared secret 1618 | * @param movingFactor the counter, time, or other value that 1619 | * changes on a per use basis. 1620 | * @param codeDigits the number of digits in the OTP, not 1621 | * including the checksum, if any. 1622 | * @param addChecksum a flag that indicates if a checksum digit 1623 | 1624 | 1625 | 1626 | M'Raihi, et al. Informational [Page 29] 1627 | 1628 | RFC 4226 HOTP Algorithm December 2005 1629 | 1630 | 1631 | * should be appended to the OTP. 1632 | * @param truncationOffset the offset into the MAC result to 1633 | * begin truncation. If this value is out of 1634 | * the range of 0 ... 15, then dynamic 1635 | * truncation will be used. 1636 | * Dynamic truncation is when the last 4 1637 | * bits of the last byte of the MAC are 1638 | * used to determine the start offset. 1639 | * @throws NoSuchAlgorithmException if no provider makes 1640 | * either HmacSHA1 or HMAC-SHA-1 1641 | * digest algorithms available. 1642 | * @throws InvalidKeyException 1643 | * The secret provided was not 1644 | * a valid HMAC-SHA-1 key. 1645 | * 1646 | * @return A numeric String in base 10 that includes 1647 | * {@link codeDigits} digits plus the optional checksum 1648 | * digit if requested. 1649 | */ 1650 | static public String generateOTP(byte[] secret, 1651 | long movingFactor, 1652 | int codeDigits, 1653 | boolean addChecksum, 1654 | int truncationOffset) 1655 | throws NoSuchAlgorithmException, InvalidKeyException 1656 | { 1657 | // put movingFactor value into text byte array 1658 | String result = null; 1659 | int digits = addChecksum ? (codeDigits + 1) : codeDigits; 1660 | byte[] text = new byte[8]; 1661 | for (int i = text.length - 1; i >= 0; i--) { 1662 | text[i] = (byte) (movingFactor & 0xff); 1663 | movingFactor >>= 8; 1664 | } 1665 | 1666 | // compute hmac hash 1667 | byte[] hash = hmac_sha1(secret, text); 1668 | 1669 | // put selected bytes into result int 1670 | int offset = hash[hash.length - 1] & 0xf; 1671 | if ( (0<=truncationOffset) && 1672 | (truncationOffset<(hash.length-4)) ) { 1673 | offset = truncationOffset; 1674 | } 1675 | int binary = 1676 | ((hash[offset] & 0x7f) << 24) 1677 | | ((hash[offset + 1] & 0xff) << 16) 1678 | | ((hash[offset + 2] & 0xff) << 8) 1679 | 1680 | 1681 | 1682 | M'Raihi, et al. Informational [Page 30] 1683 | 1684 | RFC 4226 HOTP Algorithm December 2005 1685 | 1686 | 1687 | | (hash[offset + 3] & 0xff); 1688 | 1689 | int otp = binary % DIGITS_POWER[codeDigits]; 1690 | if (addChecksum) { 1691 | otp = (otp * 10) + calcChecksum(otp, codeDigits); 1692 | } 1693 | result = Integer.toString(otp); 1694 | while (result.length() < digits) { 1695 | result = "0" + result; 1696 | } 1697 | return result; 1698 | } 1699 | } 1700 | 1701 | 1702 | 1703 | 1704 | 1705 | 1706 | 1707 | 1708 | 1709 | 1710 | 1711 | 1712 | 1713 | 1714 | 1715 | 1716 | 1717 | 1718 | 1719 | 1720 | 1721 | 1722 | 1723 | 1724 | 1725 | 1726 | 1727 | 1728 | 1729 | 1730 | 1731 | 1732 | 1733 | 1734 | 1735 | 1736 | 1737 | 1738 | M'Raihi, et al. Informational [Page 31] 1739 | 1740 | RFC 4226 HOTP Algorithm December 2005 1741 | 1742 | 1743 | Appendix D - HOTP Algorithm: Test Values 1744 | 1745 | The following test data uses the ASCII string 1746 | "12345678901234567890" for the secret: 1747 | 1748 | Secret = 0x3132333435363738393031323334353637383930 1749 | 1750 | Table 1 details for each count, the intermediate HMAC value. 1751 | 1752 | Count Hexadecimal HMAC-SHA-1(secret, count) 1753 | 0 cc93cf18508d94934c64b65d8ba7667fb7cde4b0 1754 | 1 75a48a19d4cbe100644e8ac1397eea747a2d33ab 1755 | 2 0bacb7fa082fef30782211938bc1c5e70416ff44 1756 | 3 66c28227d03a2d5529262ff016a1e6ef76557ece 1757 | 4 a904c900a64b35909874b33e61c5938a8e15ed1c 1758 | 5 a37e783d7b7233c083d4f62926c7a25f238d0316 1759 | 6 bc9cd28561042c83f219324d3c607256c03272ae 1760 | 7 a4fb960c0bc06e1eabb804e5b397cdc4b45596fa 1761 | 8 1b3c89f65e6c9e883012052823443f048b4332db 1762 | 9 1637409809a679dc698207310c8c7fc07290d9e5 1763 | 1764 | Table 2 details for each count the truncated values (both in 1765 | hexadecimal and decimal) and then the HOTP value. 1766 | 1767 | Truncated 1768 | Count Hexadecimal Decimal HOTP 1769 | 0 4c93cf18 1284755224 755224 1770 | 1 41397eea 1094287082 287082 1771 | 2 82fef30 137359152 359152 1772 | 3 66ef7655 1726969429 969429 1773 | 4 61c5938a 1640338314 338314 1774 | 5 33c083d4 868254676 254676 1775 | 6 7256c032 1918287922 287922 1776 | 7 4e5b397 82162583 162583 1777 | 8 2823443f 673399871 399871 1778 | 9 2679dc69 645520489 520489 1779 | 1780 | 1781 | 1782 | 1783 | 1784 | 1785 | 1786 | 1787 | 1788 | 1789 | 1790 | 1791 | 1792 | 1793 | 1794 | M'Raihi, et al. Informational [Page 32] 1795 | 1796 | RFC 4226 HOTP Algorithm December 2005 1797 | 1798 | 1799 | Appendix E - Extensions 1800 | 1801 | 1802 | We introduce in this section several enhancements to the HOTP 1803 | algorithm. These are not recommended extensions or part of the 1804 | standard algorithm, but merely variations that could be used for 1805 | customized implementations. 1806 | 1807 | E.1. Number of Digits 1808 | 1809 | A simple enhancement in terms of security would be to extract more 1810 | digits from the HMAC-SHA-1 value. 1811 | 1812 | For instance, calculating the HOTP value modulo 10^8 to build an 8- 1813 | digit HOTP value would reduce the probability of success of the 1814 | adversary from sv/10^6 to sv/10^8. 1815 | 1816 | This could give the opportunity to improve usability, e.g., by 1817 | increasing T and/or s, while still achieving a better security 1818 | overall. For instance, s = 10 and 10v/10^8 = v/10^7 < v/10^6 which 1819 | is the theoretical optimum for 6-digit code when s = 1. 1820 | 1821 | E.2. Alphanumeric Values 1822 | 1823 | Another option is to use A-Z and 0-9 values; or rather a subset of 32 1824 | symbols taken from the alphanumerical alphabet in order to avoid any 1825 | confusion between characters: 0, O, and Q as well as l, 1, and I are 1826 | very similar, and can look the same on a small display. 1827 | 1828 | The immediate consequence is that the security is now in the order of 1829 | sv/32^6 for a 6-digit HOTP value and sv/32^8 for an 8-digit HOTP 1830 | value. 1831 | 1832 | 32^6 > 10^9 so the security of a 6-alphanumeric HOTP code is slightly 1833 | better than a 9-digit HOTP value, which is the maximum length of an 1834 | HOTP code supported by the proposed algorithm. 1835 | 1836 | 32^8 > 10^12 so the security of an 8-alphanumeric HOTP code is 1837 | significantly better than a 9-digit HOTP value. 1838 | 1839 | Depending on the application and token/interface used for displaying 1840 | and entering the HOTP value, the choice of alphanumeric values could 1841 | be a simple and efficient way to improve security at a reduced cost 1842 | and impact on users. 1843 | 1844 | 1845 | 1846 | 1847 | 1848 | 1849 | 1850 | M'Raihi, et al. Informational [Page 33] 1851 | 1852 | RFC 4226 HOTP Algorithm December 2005 1853 | 1854 | 1855 | E.3. Sequence of HOTP Values 1856 | 1857 | As we suggested for the resynchronization to enter a short sequence 1858 | (say, 2 or 3) of HOTP values, we could generalize the concept to the 1859 | protocol, and add a parameter L that would define the length of the 1860 | HOTP sequence to enter. 1861 | 1862 | Per default, the value L SHOULD be set to 1, but if security needs to 1863 | be increased, users might be asked (possibly for a short period of 1864 | time, or a specific operation) to enter L HOTP values. 1865 | 1866 | This is another way, without increasing the HOTP length or using 1867 | alphanumeric values to tighten security. 1868 | 1869 | Note: The system MAY also be programmed to request synchronization on 1870 | a regular basis (e.g., every night, twice a week, etc.) and to 1871 | achieve this purpose, ask for a sequence of L HOTP values. 1872 | 1873 | E.4. A Counter-Based Resynchronization Method 1874 | 1875 | In this case, we assume that the client can access and send not only 1876 | the HOTP value but also other information, more specifically, the 1877 | counter value. 1878 | 1879 | A more efficient and secure method for resynchronization is possible 1880 | in this case. The client application will not send the HOTP-client 1881 | value only, but the HOTP-client and the related C-client counter 1882 | value, the HOTP value acting as a message authentication code of the 1883 | counter. 1884 | 1885 | Resynchronization Counter-based Protocol (RCP) 1886 | ---------------------------------------------- 1887 | 1888 | The server accepts if the following are all true, where C-server is 1889 | its own current counter value: 1890 | 1891 | 1) C-client >= C-server 1892 | 2) C-client - C-server <= s 1893 | 3) Check that HOTP client is valid HOTP(K,C-Client) 1894 | 4) If true, the server sets C to C-client + 1 and client is 1895 | authenticated 1896 | 1897 | In this case, there is no need for managing a look-ahead window 1898 | anymore. The probability of success of the adversary is only v/10^6 1899 | or roughly v in one million. A side benefit is obviously to be able 1900 | to increase s "infinitely" and therefore improve the system usability 1901 | without impacting the security. 1902 | 1903 | 1904 | 1905 | 1906 | M'Raihi, et al. Informational [Page 34] 1907 | 1908 | RFC 4226 HOTP Algorithm December 2005 1909 | 1910 | 1911 | This resynchronization protocol SHOULD be used whenever the related 1912 | impact on the client and server applications is deemed acceptable. 1913 | 1914 | E.5. Data Field 1915 | 1916 | Another interesting option is the introduction of a Data field, which 1917 | would be used for generating the One-Time Password values: HOTP (K, 1918 | C, [Data]) where Data is an optional field that can be the 1919 | concatenation of various pieces of identity-related information, 1920 | e.g., Data = Address | PIN. 1921 | 1922 | We could also use a Timer, either as the only moving factor or in 1923 | combination with the Counter -- in this case, e.g., Data = Timer, 1924 | where Timer could be the UNIX-time (GMT seconds since 1/1/1970) 1925 | divided by some factor (8, 16, 32, etc.) in order to give a specific 1926 | time step. The time window for the One-Time Password is then equal 1927 | to the time step multiplied by the resynchronization parameter as 1928 | defined before. For example, if we take 64 seconds as the time step 1929 | and 7 for the resynchronization parameter, we obtain an acceptance 1930 | window of +/- 3 minutes. 1931 | 1932 | Using a Data field opens for more flexibility in the algorithm 1933 | implementation, provided that the Data field is clearly specified. 1934 | 1935 | 1936 | 1937 | 1938 | 1939 | 1940 | 1941 | 1942 | 1943 | 1944 | 1945 | 1946 | 1947 | 1948 | 1949 | 1950 | 1951 | 1952 | 1953 | 1954 | 1955 | 1956 | 1957 | 1958 | 1959 | 1960 | 1961 | 1962 | M'Raihi, et al. Informational [Page 35] 1963 | 1964 | RFC 4226 HOTP Algorithm December 2005 1965 | 1966 | 1967 | Authors' Addresses 1968 | 1969 | David M'Raihi (primary contact for sending comments and questions) 1970 | VeriSign, Inc. 1971 | 685 E. Middlefield Road 1972 | Mountain View, CA 94043 USA 1973 | 1974 | Phone: 1-650-426-3832 1975 | EMail: dmraihi@verisign.com 1976 | 1977 | 1978 | Mihir Bellare 1979 | Dept of Computer Science and Engineering, Mail Code 0114 1980 | University of California at San Diego 1981 | 9500 Gilman Drive 1982 | La Jolla, CA 92093, USA 1983 | 1984 | EMail: mihir@cs.ucsd.edu 1985 | 1986 | 1987 | Frank Hoornaert 1988 | VASCO Data Security, Inc. 1989 | Koningin Astridlaan 164 1990 | 1780 Wemmel, Belgium 1991 | 1992 | EMail: frh@vasco.com 1993 | 1994 | 1995 | David Naccache 1996 | Gemplus Innovation 1997 | 34 rue Guynemer, 92447, 1998 | Issy les Moulineaux, France 1999 | and 2000 | Information Security Group, 2001 | Royal Holloway, 2002 | University of London, Egham, 2003 | Surrey TW20 0EX, UK 2004 | 2005 | EMail: david.naccache@gemplus.com, david.naccache@rhul.ac.uk 2006 | 2007 | 2008 | Ohad Ranen 2009 | Aladdin Knowledge Systems Ltd. 2010 | 15 Beit Oved Street 2011 | Tel Aviv, Israel 61110 2012 | 2013 | EMail: Ohad.Ranen@ealaddin.com 2014 | 2015 | 2016 | 2017 | 2018 | M'Raihi, et al. Informational [Page 36] 2019 | 2020 | RFC 4226 HOTP Algorithm December 2005 2021 | 2022 | 2023 | Full Copyright Statement 2024 | 2025 | Copyright (C) The Internet Society (2005). 2026 | 2027 | This document is subject to the rights, licenses and restrictions 2028 | contained in BCP 78, and except as set forth therein, the authors 2029 | retain all their rights. 2030 | 2031 | This document and the information contained herein are provided on an 2032 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS 2033 | OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET 2034 | ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, 2035 | INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE 2036 | INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED 2037 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 2038 | 2039 | Intellectual Property 2040 | 2041 | The IETF takes no position regarding the validity or scope of any 2042 | Intellectual Property Rights or other rights that might be claimed to 2043 | pertain to the implementation or use of the technology described in 2044 | this document or the extent to which any license under such rights 2045 | might or might not be available; nor does it represent that it has 2046 | made any independent effort to identify any such rights. Information 2047 | on the procedures with respect to rights in RFC documents can be 2048 | found in BCP 78 and BCP 79. 2049 | 2050 | Copies of IPR disclosures made to the IETF Secretariat and any 2051 | assurances of licenses to be made available, or the result of an 2052 | attempt made to obtain a general license or permission for the use of 2053 | such proprietary rights by implementers or users of this 2054 | specification can be obtained from the IETF on-line IPR repository at 2055 | http://www.ietf.org/ipr. 2056 | 2057 | The IETF invites any interested party to bring to its attention any 2058 | copyrights, patents or patent applications, or other proprietary 2059 | rights that may cover technology that may be required to implement 2060 | this standard. Please address the information to the IETF at ietf- 2061 | ipr@ietf.org. 2062 | 2063 | Acknowledgement 2064 | 2065 | Funding for the RFC Editor function is currently provided by the 2066 | Internet Society. 2067 | 2068 | 2069 | 2070 | 2071 | 2072 | 2073 | 2074 | M'Raihi, et al. Informational [Page 37] 2075 | 2076 | -------------------------------------------------------------------------------- /rfc6238.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Internet Engineering Task Force (IETF) D. M'Raihi 8 | Request for Comments: 6238 Verisign, Inc. 9 | Category: Informational S. Machani 10 | ISSN: 2070-1721 Diversinet Corp. 11 | M. Pei 12 | Symantec 13 | J. Rydell 14 | Portwise, Inc. 15 | May 2011 16 | 17 | 18 | TOTP: Time-Based One-Time Password Algorithm 19 | 20 | Abstract 21 | 22 | This document describes an extension of the One-Time Password (OTP) 23 | algorithm, namely the HMAC-based One-Time Password (HOTP) algorithm, 24 | as defined in RFC 4226, to support the time-based moving factor. The 25 | HOTP algorithm specifies an event-based OTP algorithm, where the 26 | moving factor is an event counter. The present work bases the moving 27 | factor on a time value. A time-based variant of the OTP algorithm 28 | provides short-lived OTP values, which are desirable for enhanced 29 | security. 30 | 31 | The proposed algorithm can be used across a wide range of network 32 | applications, from remote Virtual Private Network (VPN) access and 33 | Wi-Fi network logon to transaction-oriented Web applications. The 34 | authors believe that a common and shared algorithm will facilitate 35 | adoption of two-factor authentication on the Internet by enabling 36 | interoperability across commercial and open-source implementations. 37 | 38 | Status of This Memo 39 | 40 | This document is not an Internet Standards Track specification; it is 41 | published for informational purposes. 42 | 43 | This document is a product of the Internet Engineering Task Force 44 | (IETF). It represents the consensus of the IETF community. It has 45 | received public review and has been approved for publication by the 46 | Internet Engineering Steering Group (IESG). Not all documents 47 | approved by the IESG are a candidate for any level of Internet 48 | Standard; see Section 2 of RFC 5741. 49 | 50 | Information about the current status of this document, any errata, 51 | and how to provide feedback on it may be obtained at 52 | http://www.rfc-editor.org/info/rfc6238. 53 | 54 | 55 | 56 | 57 | 58 | M'Raihi, et al. Informational [Page 1] 59 | 60 | RFC 6238 HOTPTimeBased May 2011 61 | 62 | 63 | Copyright Notice 64 | 65 | Copyright (c) 2011 IETF Trust and the persons identified as the 66 | document authors. All rights reserved. 67 | 68 | This document is subject to BCP 78 and the IETF Trust's Legal 69 | Provisions Relating to IETF Documents 70 | (http://trustee.ietf.org/license-info) in effect on the date of 71 | publication of this document. Please review these documents 72 | carefully, as they describe your rights and restrictions with respect 73 | to this document. Code Components extracted from this document must 74 | include Simplified BSD License text as described in Section 4.e of 75 | the Trust Legal Provisions and are provided without warranty as 76 | described in the Simplified BSD License. 77 | 78 | Table of Contents 79 | 80 | 1. Introduction ....................................................2 81 | 1.1. Scope ......................................................2 82 | 1.2. Background .................................................3 83 | 2. Notation and Terminology ........................................3 84 | 3. Algorithm Requirements ..........................................3 85 | 4. TOTP Algorithm ..................................................4 86 | 4.1. Notations ..................................................4 87 | 4.2. Description ................................................4 88 | 5. Security Considerations .........................................5 89 | 5.1. General ....................................................5 90 | 5.2. Validation and Time-Step Size ..............................6 91 | 6. Resynchronization ...............................................7 92 | 7. Acknowledgements ................................................7 93 | 8. References ......................................................8 94 | 8.1. Normative References .......................................8 95 | 8.2. Informative References .....................................8 96 | Appendix A. TOTP Algorithm: Reference Implementation ...............9 97 | Appendix B. Test Vectors ..........................................14 98 | 99 | 1. Introduction 100 | 101 | 1.1. Scope 102 | 103 | This document describes an extension of the One-Time Password (OTP) 104 | algorithm, namely the HMAC-based One-Time Password (HOTP) algorithm, 105 | as defined in [RFC4226], to support the time-based moving factor. 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | M'Raihi, et al. Informational [Page 2] 115 | 116 | RFC 6238 HOTPTimeBased May 2011 117 | 118 | 119 | 1.2. Background 120 | 121 | As defined in [RFC4226], the HOTP algorithm is based on the 122 | HMAC-SHA-1 algorithm (as specified in [RFC2104]) and applied to an 123 | increasing counter value representing the message in the HMAC 124 | computation. 125 | 126 | Basically, the output of the HMAC-SHA-1 calculation is truncated to 127 | obtain user-friendly values: 128 | 129 | HOTP(K,C) = Truncate(HMAC-SHA-1(K,C)) 130 | 131 | where Truncate represents the function that can convert an HMAC-SHA-1 132 | value into an HOTP value. K and C represent the shared secret and 133 | counter value; see [RFC4226] for detailed definitions. 134 | 135 | TOTP is the time-based variant of this algorithm, where a value T, 136 | derived from a time reference and a time step, replaces the counter C 137 | in the HOTP computation. 138 | 139 | TOTP implementations MAY use HMAC-SHA-256 or HMAC-SHA-512 functions, 140 | based on SHA-256 or SHA-512 [SHA2] hash functions, instead of the 141 | HMAC-SHA-1 function that has been specified for the HOTP computation 142 | in [RFC4226]. 143 | 144 | 2. Notation and Terminology 145 | 146 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 147 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 148 | document are to be interpreted as described in [RFC2119]. 149 | 150 | 3. Algorithm Requirements 151 | 152 | This section summarizes the requirements taken into account for 153 | designing the TOTP algorithm. 154 | 155 | R1: The prover (e.g., token, soft token) and verifier (authentication 156 | or validation server) MUST know or be able to derive the current 157 | Unix time (i.e., the number of seconds elapsed since midnight UTC 158 | of January 1, 1970) for OTP generation. See [UT] for a more 159 | detailed definition of the commonly known "Unix time". The 160 | precision of the time used by the prover affects how often the 161 | clock synchronization should be done; see Section 6. 162 | 163 | R2: The prover and verifier MUST either share the same secret or the 164 | knowledge of a secret transformation to generate a shared secret. 165 | 166 | R3: The algorithm MUST use HOTP [RFC4226] as a key building block. 167 | 168 | 169 | 170 | M'Raihi, et al. Informational [Page 3] 171 | 172 | RFC 6238 HOTPTimeBased May 2011 173 | 174 | 175 | R4: The prover and verifier MUST use the same time-step value X. 176 | 177 | R5: There MUST be a unique secret (key) for each prover. 178 | 179 | R6: The keys SHOULD be randomly generated or derived using key 180 | derivation algorithms. 181 | 182 | R7: The keys MAY be stored in a tamper-resistant device and SHOULD be 183 | protected against unauthorized access and usage. 184 | 185 | 4. TOTP Algorithm 186 | 187 | This variant of the HOTP algorithm specifies the calculation of a 188 | one-time password value, based on a representation of the counter as 189 | a time factor. 190 | 191 | 4.1. Notations 192 | 193 | o X represents the time step in seconds (default value X = 194 | 30 seconds) and is a system parameter. 195 | 196 | o T0 is the Unix time to start counting time steps (default value is 197 | 0, i.e., the Unix epoch) and is also a system parameter. 198 | 199 | 4.2. Description 200 | 201 | Basically, we define TOTP as TOTP = HOTP(K, T), where T is an integer 202 | and represents the number of time steps between the initial counter 203 | time T0 and the current Unix time. 204 | 205 | More specifically, T = (Current Unix time - T0) / X, where the 206 | default floor function is used in the computation. 207 | 208 | For example, with T0 = 0 and Time Step X = 30, T = 1 if the current 209 | Unix time is 59 seconds, and T = 2 if the current Unix time is 210 | 60 seconds. 211 | 212 | The implementation of this algorithm MUST support a time value T 213 | larger than a 32-bit integer when it is beyond the year 2038. The 214 | value of the system parameters X and T0 are pre-established during 215 | the provisioning process and communicated between a prover and 216 | verifier as part of the provisioning step. The provisioning flow is 217 | out of scope of this document; refer to [RFC6030] for such 218 | provisioning container specifications. 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | M'Raihi, et al. Informational [Page 4] 227 | 228 | RFC 6238 HOTPTimeBased May 2011 229 | 230 | 231 | 5. Security Considerations 232 | 233 | 5.1. General 234 | 235 | The security and strength of this algorithm depend on the properties 236 | of the underlying building block HOTP, which is a construction based 237 | on HMAC [RFC2104] using SHA-1 as the hash function. 238 | 239 | The conclusion of the security analysis detailed in [RFC4226] is 240 | that, for all practical purposes, the outputs of the dynamic 241 | truncation on distinct inputs are uniformly and independently 242 | distributed strings. 243 | 244 | The analysis demonstrates that the best possible attack against the 245 | HOTP function is the brute force attack. 246 | 247 | As indicated in the algorithm requirement section, keys SHOULD be 248 | chosen at random or using a cryptographically strong pseudorandom 249 | generator properly seeded with a random value. 250 | 251 | Keys SHOULD be of the length of the HMAC output to facilitate 252 | interoperability. 253 | 254 | We RECOMMEND following the recommendations in [RFC4086] for all 255 | pseudorandom and random number generations. The pseudorandom numbers 256 | used for generating the keys SHOULD successfully pass the randomness 257 | test specified in [CN], or a similar well-recognized test. 258 | 259 | All the communications SHOULD take place over a secure channel, e.g., 260 | Secure Socket Layer/Transport Layer Security (SSL/TLS) [RFC5246] or 261 | IPsec connections [RFC4301]. 262 | 263 | We also RECOMMEND storing the keys securely in the validation system, 264 | and, more specifically, encrypting them using tamper-resistant 265 | hardware encryption and exposing them only when required: for 266 | example, the key is decrypted when needed to verify an OTP value, and 267 | re-encrypted immediately to limit exposure in the RAM to a short 268 | period of time. 269 | 270 | The key store MUST be in a secure area, to avoid, as much as 271 | possible, direct attack on the validation system and secrets 272 | database. Particularly, access to the key material should be limited 273 | to programs and processes required by the validation system only. 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | M'Raihi, et al. Informational [Page 5] 283 | 284 | RFC 6238 HOTPTimeBased May 2011 285 | 286 | 287 | 5.2. Validation and Time-Step Size 288 | 289 | An OTP generated within the same time step will be the same. When an 290 | OTP is received at a validation system, it doesn't know a client's 291 | exact timestamp when an OTP was generated. The validation system may 292 | typically use the timestamp when an OTP is received for OTP 293 | comparison. Due to network latency, the gap (as measured by T, that 294 | is, the number of time steps since T0) between the time that the OTP 295 | was generated and the time that the OTP arrives at the receiving 296 | system may be large. The receiving time at the validation system and 297 | the actual OTP generation may not fall within the same time-step 298 | window that produced the same OTP. When an OTP is generated at the 299 | end of a time-step window, the receiving time most likely falls into 300 | the next time-step window. A validation system SHOULD typically set 301 | a policy for an acceptable OTP transmission delay window for 302 | validation. The validation system should compare OTPs not only with 303 | the receiving timestamp but also the past timestamps that are within 304 | the transmission delay. A larger acceptable delay window would 305 | expose a larger window for attacks. We RECOMMEND that at most one 306 | time step is allowed as the network delay. 307 | 308 | The time-step size has an impact on both security and usability. A 309 | larger time-step size means a larger validity window for an OTP to be 310 | accepted by a validation system. There are implications for using a 311 | larger time-step size, as follows: 312 | 313 | First, a larger time-step size exposes a larger window to attack. 314 | When an OTP is generated and exposed to a third party before it is 315 | consumed, the third party can consume the OTP within the time-step 316 | window. 317 | 318 | We RECOMMEND a default time-step size of 30 seconds. This default 319 | value of 30 seconds is selected as a balance between security and 320 | usability. 321 | 322 | Second, the next different OTP must be generated in the next time- 323 | step window. A user must wait until the clock moves to the next 324 | time-step window from the last submission. The waiting time may not 325 | be exactly the length of the time step, depending on when the last 326 | OTP was generated. For example, if the last OTP was generated at the 327 | halfway point in a time-step window, the waiting time for the next 328 | OTP is half the length of the time step. In general, a larger time- 329 | step window means a longer waiting time for a user to get the next 330 | valid OTP after the last successful OTP validation. A too-large 331 | window (for example, 10 minutes) most probably won't be suitable for 332 | typical Internet login use cases; a user may not be able to get the 333 | next OTP within 10 minutes and therefore will have to re-login to the 334 | same site in 10 minutes. 335 | 336 | 337 | 338 | M'Raihi, et al. Informational [Page 6] 339 | 340 | RFC 6238 HOTPTimeBased May 2011 341 | 342 | 343 | Note that a prover may send the same OTP inside a given time-step 344 | window multiple times to a verifier. The verifier MUST NOT accept 345 | the second attempt of the OTP after the successful validation has 346 | been issued for the first OTP, which ensures one-time only use of an 347 | OTP. 348 | 349 | 6. Resynchronization 350 | 351 | Because of possible clock drifts between a client and a validation 352 | server, we RECOMMEND that the validator be set with a specific limit 353 | to the number of time steps a prover can be "out of synch" before 354 | being rejected. 355 | 356 | This limit can be set both forward and backward from the calculated 357 | time step on receipt of the OTP value. If the time step is 358 | 30 seconds as recommended, and the validator is set to only accept 359 | two time steps backward, then the maximum elapsed time drift would be 360 | around 89 seconds, i.e., 29 seconds in the calculated time step and 361 | 60 seconds for two backward time steps. 362 | 363 | This would mean the validator could perform a validation against the 364 | current time and then two further validations for each backward step 365 | (for a total of 3 validations). Upon successful validation, the 366 | validation server can record the detected clock drift for the token 367 | in terms of the number of time steps. When a new OTP is received 368 | after this step, the validator can validate the OTP with the current 369 | timestamp adjusted with the recorded number of time-step clock drifts 370 | for the token. 371 | 372 | Also, it is important to note that the longer a prover has not sent 373 | an OTP to a validation system, the longer (potentially) the 374 | accumulated clock drift between the prover and the verifier. In such 375 | cases, the automatic resynchronization described above may not work 376 | if the drift exceeds the allowed threshold. Additional 377 | authentication measures should be used to safely authenticate the 378 | prover and explicitly resynchronize the clock drift between the 379 | prover and the validator. 380 | 381 | 7. Acknowledgements 382 | 383 | The authors of this document would like to thank the following people 384 | for their contributions and support to make this a better 385 | specification: Hannes Tschofenig, Jonathan Tuliani, David Dix, 386 | Siddharth Bajaj, Stu Veath, Shuh Chang, Oanh Hoang, John Huang, and 387 | Siddhartha Mohapatra. 388 | 389 | 390 | 391 | 392 | 393 | 394 | M'Raihi, et al. Informational [Page 7] 395 | 396 | RFC 6238 HOTPTimeBased May 2011 397 | 398 | 399 | 8. References 400 | 401 | 8.1. Normative References 402 | 403 | [RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed- 404 | Hashing for Message Authentication", RFC 2104, 405 | February 1997. 406 | 407 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate 408 | Requirement Levels", BCP 14, RFC 2119, March 1997. 409 | 410 | [RFC4086] Eastlake 3rd, D., Schiller, J., and S. Crocker, 411 | "Randomness Recommendations for Security", BCP 106, 412 | RFC 4086, June 2005. 413 | 414 | [RFC4226] M'Raihi, D., Bellare, M., Hoornaert, F., Naccache, D., and 415 | O. Ranen, "HOTP: An HMAC-Based One-Time Password 416 | Algorithm", RFC 4226, December 2005. 417 | 418 | [SHA2] NIST, "FIPS PUB 180-3: Secure Hash Standard (SHS)", 419 | October 2008, . 421 | 422 | 8.2. Informative References 423 | 424 | [CN] Coron, J. and D. Naccache, "An Accurate Evaluation of 425 | Maurer's Universal Test", LNCS 1556, February 1999, 426 | . 428 | 429 | [RFC4301] Kent, S. and K. Seo, "Security Architecture for the 430 | Internet Protocol", RFC 4301, December 2005. 431 | 432 | [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security 433 | (TLS) Protocol Version 1.2", RFC 5246, August 2008. 434 | 435 | [RFC6030] Hoyer, P., Pei, M., and S. Machani, "Portable Symmetric 436 | Key Container (PSKC)", RFC 6030, October 2010. 437 | 438 | [UT] Wikipedia, "Unix time", February 2011, 439 | . 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | M'Raihi, et al. Informational [Page 8] 451 | 452 | RFC 6238 HOTPTimeBased May 2011 453 | 454 | 455 | Appendix A. TOTP Algorithm: Reference Implementation 456 | 457 | 458 | 459 | /** 460 | Copyright (c) 2011 IETF Trust and the persons identified as 461 | authors of the code. All rights reserved. 462 | 463 | Redistribution and use in source and binary forms, with or without 464 | modification, is permitted pursuant to, and subject to the license 465 | terms contained in, the Simplified BSD License set forth in Section 466 | 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents 467 | (http://trustee.ietf.org/license-info). 468 | */ 469 | 470 | import java.lang.reflect.UndeclaredThrowableException; 471 | import java.security.GeneralSecurityException; 472 | import java.text.DateFormat; 473 | import java.text.SimpleDateFormat; 474 | import java.util.Date; 475 | import javax.crypto.Mac; 476 | import javax.crypto.spec.SecretKeySpec; 477 | import java.math.BigInteger; 478 | import java.util.TimeZone; 479 | 480 | 481 | /** 482 | * This is an example implementation of the OATH 483 | * TOTP algorithm. 484 | * Visit www.openauthentication.org for more information. 485 | * 486 | * @author Johan Rydell, PortWise, Inc. 487 | */ 488 | 489 | public class TOTP { 490 | 491 | private TOTP() {} 492 | 493 | /** 494 | * This method uses the JCE to provide the crypto algorithm. 495 | * HMAC computes a Hashed Message Authentication Code with the 496 | * crypto hash algorithm as a parameter. 497 | * 498 | * @param crypto: the crypto algorithm (HmacSHA1, HmacSHA256, 499 | * HmacSHA512) 500 | * @param keyBytes: the bytes to use for the HMAC key 501 | * @param text: the message or text to be authenticated 502 | */ 503 | 504 | 505 | 506 | M'Raihi, et al. Informational [Page 9] 507 | 508 | RFC 6238 HOTPTimeBased May 2011 509 | 510 | 511 | private static byte[] hmac_sha(String crypto, byte[] keyBytes, 512 | byte[] text){ 513 | try { 514 | Mac hmac; 515 | hmac = Mac.getInstance(crypto); 516 | SecretKeySpec macKey = 517 | new SecretKeySpec(keyBytes, "RAW"); 518 | hmac.init(macKey); 519 | return hmac.doFinal(text); 520 | } catch (GeneralSecurityException gse) { 521 | throw new UndeclaredThrowableException(gse); 522 | } 523 | } 524 | 525 | 526 | /** 527 | * This method converts a HEX string to Byte[] 528 | * 529 | * @param hex: the HEX string 530 | * 531 | * @return: a byte array 532 | */ 533 | 534 | private static byte[] hexStr2Bytes(String hex){ 535 | // Adding one byte to get the right conversion 536 | // Values starting with "0" can be converted 537 | byte[] bArray = new BigInteger("10" + hex,16).toByteArray(); 538 | 539 | // Copy all the REAL bytes, not the "first" 540 | byte[] ret = new byte[bArray.length - 1]; 541 | for (int i = 0; i < ret.length; i++) 542 | ret[i] = bArray[i+1]; 543 | return ret; 544 | } 545 | 546 | private static final int[] DIGITS_POWER 547 | // 0 1 2 3 4 5 6 7 8 548 | = {1,10,100,1000,10000,100000,1000000,10000000,100000000 }; 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | M'Raihi, et al. Informational [Page 10] 563 | 564 | RFC 6238 HOTPTimeBased May 2011 565 | 566 | 567 | /** 568 | * This method generates a TOTP value for the given 569 | * set of parameters. 570 | * 571 | * @param key: the shared secret, HEX encoded 572 | * @param time: a value that reflects a time 573 | * @param returnDigits: number of digits to return 574 | * 575 | * @return: a numeric String in base 10 that includes 576 | * {@link truncationDigits} digits 577 | */ 578 | 579 | public static String generateTOTP(String key, 580 | String time, 581 | String returnDigits){ 582 | return generateTOTP(key, time, returnDigits, "HmacSHA1"); 583 | } 584 | 585 | 586 | /** 587 | * This method generates a TOTP value for the given 588 | * set of parameters. 589 | * 590 | * @param key: the shared secret, HEX encoded 591 | * @param time: a value that reflects a time 592 | * @param returnDigits: number of digits to return 593 | * 594 | * @return: a numeric String in base 10 that includes 595 | * {@link truncationDigits} digits 596 | */ 597 | 598 | public static String generateTOTP256(String key, 599 | String time, 600 | String returnDigits){ 601 | return generateTOTP(key, time, returnDigits, "HmacSHA256"); 602 | } 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | M'Raihi, et al. Informational [Page 11] 619 | 620 | RFC 6238 HOTPTimeBased May 2011 621 | 622 | 623 | /** 624 | * This method generates a TOTP value for the given 625 | * set of parameters. 626 | * 627 | * @param key: the shared secret, HEX encoded 628 | * @param time: a value that reflects a time 629 | * @param returnDigits: number of digits to return 630 | * 631 | * @return: a numeric String in base 10 that includes 632 | * {@link truncationDigits} digits 633 | */ 634 | 635 | public static String generateTOTP512(String key, 636 | String time, 637 | String returnDigits){ 638 | return generateTOTP(key, time, returnDigits, "HmacSHA512"); 639 | } 640 | 641 | 642 | /** 643 | * This method generates a TOTP value for the given 644 | * set of parameters. 645 | * 646 | * @param key: the shared secret, HEX encoded 647 | * @param time: a value that reflects a time 648 | * @param returnDigits: number of digits to return 649 | * @param crypto: the crypto function to use 650 | * 651 | * @return: a numeric String in base 10 that includes 652 | * {@link truncationDigits} digits 653 | */ 654 | 655 | public static String generateTOTP(String key, 656 | String time, 657 | String returnDigits, 658 | String crypto){ 659 | int codeDigits = Integer.decode(returnDigits).intValue(); 660 | String result = null; 661 | 662 | // Using the counter 663 | // First 8 bytes are for the movingFactor 664 | // Compliant with base RFC 4226 (HOTP) 665 | while (time.length() < 16 ) 666 | time = "0" + time; 667 | 668 | // Get the HEX in a Byte[] 669 | byte[] msg = hexStr2Bytes(time); 670 | byte[] k = hexStr2Bytes(key); 671 | 672 | 673 | 674 | M'Raihi, et al. Informational [Page 12] 675 | 676 | RFC 6238 HOTPTimeBased May 2011 677 | 678 | 679 | byte[] hash = hmac_sha(crypto, k, msg); 680 | 681 | // put selected bytes into result int 682 | int offset = hash[hash.length - 1] & 0xf; 683 | 684 | int binary = 685 | ((hash[offset] & 0x7f) << 24) | 686 | ((hash[offset + 1] & 0xff) << 16) | 687 | ((hash[offset + 2] & 0xff) << 8) | 688 | (hash[offset + 3] & 0xff); 689 | 690 | int otp = binary % DIGITS_POWER[codeDigits]; 691 | 692 | result = Integer.toString(otp); 693 | while (result.length() < codeDigits) { 694 | result = "0" + result; 695 | } 696 | return result; 697 | } 698 | 699 | public static void main(String[] args) { 700 | // Seed for HMAC-SHA1 - 20 bytes 701 | String seed = "3132333435363738393031323334353637383930"; 702 | // Seed for HMAC-SHA256 - 32 bytes 703 | String seed32 = "3132333435363738393031323334353637383930" + 704 | "313233343536373839303132"; 705 | // Seed for HMAC-SHA512 - 64 bytes 706 | String seed64 = "3132333435363738393031323334353637383930" + 707 | "3132333435363738393031323334353637383930" + 708 | "3132333435363738393031323334353637383930" + 709 | "31323334"; 710 | long T0 = 0; 711 | long X = 30; 712 | long testTime[] = {59L, 1111111109L, 1111111111L, 713 | 1234567890L, 2000000000L, 20000000000L}; 714 | 715 | String steps = "0"; 716 | DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 717 | df.setTimeZone(TimeZone.getTimeZone("UTC")); 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | M'Raihi, et al. Informational [Page 13] 731 | 732 | RFC 6238 HOTPTimeBased May 2011 733 | 734 | 735 | try { 736 | System.out.println( 737 | "+---------------+-----------------------+" + 738 | "------------------+--------+--------+"); 739 | System.out.println( 740 | "| Time(sec) | Time (UTC format) " + 741 | "| Value of T(Hex) | TOTP | Mode |"); 742 | System.out.println( 743 | "+---------------+-----------------------+" + 744 | "------------------+--------+--------+"); 745 | 746 | for (int i=0; i 776 | 777 | Appendix B. Test Vectors 778 | 779 | This section provides test values that can be used for the HOTP time- 780 | based variant algorithm interoperability test. 781 | 782 | 783 | 784 | 785 | 786 | M'Raihi, et al. Informational [Page 14] 787 | 788 | RFC 6238 HOTPTimeBased May 2011 789 | 790 | 791 | The test token shared secret uses the ASCII string value 792 | "12345678901234567890". With Time Step X = 30, and the Unix epoch as 793 | the initial value to count time steps, where T0 = 0, the TOTP 794 | algorithm will display the following values for specified modes and 795 | timestamps. 796 | 797 | +-------------+--------------+------------------+----------+--------+ 798 | | Time (sec) | UTC Time | Value of T (hex) | TOTP | Mode | 799 | +-------------+--------------+------------------+----------+--------+ 800 | | 59 | 1970-01-01 | 0000000000000001 | 94287082 | SHA1 | 801 | | | 00:00:59 | | | | 802 | | 59 | 1970-01-01 | 0000000000000001 | 46119246 | SHA256 | 803 | | | 00:00:59 | | | | 804 | | 59 | 1970-01-01 | 0000000000000001 | 90693936 | SHA512 | 805 | | | 00:00:59 | | | | 806 | | 1111111109 | 2005-03-18 | 00000000023523EC | 07081804 | SHA1 | 807 | | | 01:58:29 | | | | 808 | | 1111111109 | 2005-03-18 | 00000000023523EC | 68084774 | SHA256 | 809 | | | 01:58:29 | | | | 810 | | 1111111109 | 2005-03-18 | 00000000023523EC | 25091201 | SHA512 | 811 | | | 01:58:29 | | | | 812 | | 1111111111 | 2005-03-18 | 00000000023523ED | 14050471 | SHA1 | 813 | | | 01:58:31 | | | | 814 | | 1111111111 | 2005-03-18 | 00000000023523ED | 67062674 | SHA256 | 815 | | | 01:58:31 | | | | 816 | | 1111111111 | 2005-03-18 | 00000000023523ED | 99943326 | SHA512 | 817 | | | 01:58:31 | | | | 818 | | 1234567890 | 2009-02-13 | 000000000273EF07 | 89005924 | SHA1 | 819 | | | 23:31:30 | | | | 820 | | 1234567890 | 2009-02-13 | 000000000273EF07 | 91819424 | SHA256 | 821 | | | 23:31:30 | | | | 822 | | 1234567890 | 2009-02-13 | 000000000273EF07 | 93441116 | SHA512 | 823 | | | 23:31:30 | | | | 824 | | 2000000000 | 2033-05-18 | 0000000003F940AA | 69279037 | SHA1 | 825 | | | 03:33:20 | | | | 826 | | 2000000000 | 2033-05-18 | 0000000003F940AA | 90698825 | SHA256 | 827 | | | 03:33:20 | | | | 828 | | 2000000000 | 2033-05-18 | 0000000003F940AA | 38618901 | SHA512 | 829 | | | 03:33:20 | | | | 830 | | 20000000000 | 2603-10-11 | 0000000027BC86AA | 65353130 | SHA1 | 831 | | | 11:33:20 | | | | 832 | | 20000000000 | 2603-10-11 | 0000000027BC86AA | 77737706 | SHA256 | 833 | | | 11:33:20 | | | | 834 | | 20000000000 | 2603-10-11 | 0000000027BC86AA | 47863826 | SHA512 | 835 | | | 11:33:20 | | | | 836 | +-------------+--------------+------------------+----------+--------+ 837 | 838 | Table 1: TOTP Table 839 | 840 | 841 | 842 | M'Raihi, et al. Informational [Page 15] 843 | 844 | RFC 6238 HOTPTimeBased May 2011 845 | 846 | 847 | Authors' Addresses 848 | 849 | David M'Raihi 850 | Verisign, Inc. 851 | 685 E. Middlefield Road 852 | Mountain View, CA 94043 853 | USA 854 | 855 | EMail: davidietf@gmail.com 856 | 857 | 858 | Salah Machani 859 | Diversinet Corp. 860 | 2225 Sheppard Avenue East, Suite 1801 861 | Toronto, Ontario M2J 5C2 862 | Canada 863 | 864 | EMail: smachani@diversinet.com 865 | 866 | 867 | Mingliang Pei 868 | Symantec 869 | 510 E. Middlefield Road 870 | Mountain View, CA 94043 871 | USA 872 | 873 | EMail: Mingliang_Pei@symantec.com 874 | 875 | 876 | Johan Rydell 877 | Portwise, Inc. 878 | 275 Hawthorne Ave., Suite 119 879 | Palo Alto, CA 94301 880 | USA 881 | 882 | EMail: johanietf@gmail.com 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | M'Raihi, et al. Informational [Page 16] 899 | 900 | -------------------------------------------------------------------------------- /totp.go: -------------------------------------------------------------------------------- 1 | package twofactor 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/hmac" 7 | "crypto/rand" 8 | "crypto/sha1" 9 | "crypto/sha256" 10 | "crypto/sha512" 11 | "encoding/base32" 12 | "encoding/hex" 13 | "errors" 14 | "fmt" 15 | "hash" 16 | "io" 17 | "math" 18 | "net/url" 19 | "strconv" 20 | "time" 21 | 22 | "github.com/sec51/convert" 23 | "github.com/sec51/convert/bigendian" 24 | "github.com/sec51/cryptoengine" 25 | qr "github.com/sec51/qrcode" 26 | ) 27 | 28 | const ( 29 | backoff_minutes = 5 // this is the time to wait before verifying another token 30 | max_failures = 3 // total amount of failures, after that the user needs to wait for the backoff time 31 | counter_size = 8 // this is defined in the RFC 4226 32 | message_type = 0 // this is the message type for the crypto engine 33 | ) 34 | 35 | var ( 36 | initializationFailedError = errors.New("Totp has not been initialized correctly") 37 | LockDownError = errors.New("The verification is locked down, because of too many trials.") 38 | ) 39 | 40 | // WARNING: The `Totp` struct should never be instantiated manually! 41 | // Use the `NewTOTP` function 42 | type Totp struct { 43 | key []byte // this is the secret key 44 | counter [counter_size]byte // this is the counter used to synchronize with the client device 45 | digits int // total amount of digits of the code displayed on the device 46 | issuer string // the company which issues the 2FA 47 | account string // usually the user email or the account id 48 | stepSize int // by default 30 seconds 49 | clientOffset int // the amount of steps the client is off 50 | totalVerificationFailures int // the total amount of verification failures from the client - by default 10 51 | lastVerificationTime time.Time // the last verification executed 52 | hashFunction crypto.Hash // the hash function used in the HMAC construction (sha1 - sha156 - sha512) 53 | } 54 | 55 | // This function is used to synchronize the counter with the client 56 | // Offset can be a negative number as well 57 | // Usually it's either -1, 0 or 1 58 | // This is used internally 59 | func (otp *Totp) synchronizeCounter(offset int) { 60 | otp.clientOffset = offset 61 | } 62 | 63 | // Label returns the combination of issuer:account string 64 | func (otp *Totp) label() string { 65 | return fmt.Sprintf("%s:%s", url.QueryEscape(otp.issuer), otp.account) 66 | } 67 | 68 | // Counter returns the TOTP's 8-byte counter as unsigned 64-bit integer. 69 | func (otp *Totp) getIntCounter() uint64 { 70 | return bigendian.FromUint64(otp.counter) 71 | } 72 | 73 | // This function creates a new TOTP object 74 | // This is the function which is needed to start the whole process 75 | // account: usually the user email 76 | // issuer: the name of the company/service 77 | // hash: is the crypto function used: crypto.SHA1, crypto.SHA256, crypto.SHA512 78 | // digits: is the token amount of digits (6 or 7 or 8) 79 | // steps: the amount of second the token is valid 80 | // it automatically generates a secret key using the golang crypto rand package. If there is not enough entropy the function returns an error 81 | // The key is not encrypted in this package. It's a secret key. Therefore if you transfer the key bytes in the network, 82 | // please take care of protecting the key or in fact all the bytes. 83 | func NewTOTP(account, issuer string, hash crypto.Hash, digits int) (*Totp, error) { 84 | 85 | keySize := hash.Size() 86 | key := make([]byte, keySize) 87 | total, err := rand.Read(key) 88 | if err != nil { 89 | return nil, errors.New(fmt.Sprintf("TOTP failed to create because there is not enough entropy, we got only %d random bytes", total)) 90 | } 91 | 92 | // sanitize the digits range otherwise it may create invalid tokens ! 93 | if digits < 6 || digits > 8 { 94 | digits = 8 95 | } 96 | 97 | return makeTOTP(key, account, issuer, hash, digits) 98 | 99 | } 100 | 101 | // Private function which initialize the TOTP so that it's easier to unit test it 102 | // Used internally 103 | func makeTOTP(key []byte, account, issuer string, hash crypto.Hash, digits int) (*Totp, error) { 104 | otp := new(Totp) 105 | otp.key = key 106 | otp.account = account 107 | otp.issuer = issuer 108 | otp.digits = digits 109 | otp.stepSize = 30 // we set it to 30 seconds which is the recommended value from the RFC 110 | otp.clientOffset = 0 111 | otp.hashFunction = hash 112 | return otp, nil 113 | } 114 | 115 | // This function validates the user provided token 116 | // It calculates 3 different tokens. The current one, one before now and one after now. 117 | // The difference is driven by the TOTP step size 118 | // Based on which of the 3 steps it succeeds to validates, the client offset is updated. 119 | // It also updates the total amount of verification failures and the last time a verification happened in UTC time 120 | // Returns an error in case of verification failure, with the reason 121 | // There is a very basic method which protects from timing attacks, although if the step time used is low it should not be necessary 122 | // An attacker can still learn the synchronization offset. This is however irrelevant because the attacker has then 30 seconds to 123 | // guess the code and after 3 failures the function returns an error for the following 5 minutes 124 | func (otp *Totp) Validate(userCode string) error { 125 | 126 | // check Totp initialization 127 | if err := totpHasBeenInitialized(otp); err != nil { 128 | return err 129 | } 130 | 131 | // verify that the token is valid 132 | if userCode == "" { 133 | return errors.New("User provided token is empty") 134 | } 135 | 136 | // check against the total amount of failures 137 | if otp.totalVerificationFailures >= max_failures && !validBackoffTime(otp.lastVerificationTime) { 138 | return LockDownError 139 | } 140 | 141 | if otp.totalVerificationFailures >= max_failures && validBackoffTime(otp.lastVerificationTime) { 142 | // reset the total verification failures counter 143 | otp.totalVerificationFailures = 0 144 | } 145 | 146 | // calculate the sha256 of the user code 147 | userTokenHash := sha256.Sum256([]byte(userCode)) 148 | userToken := hex.EncodeToString(userTokenHash[:]) 149 | 150 | // 1 calculate the 3 tokens 151 | tokens := make([]string, 3) 152 | token0Hash := sha256.Sum256([]byte(calculateTOTP(otp, -1))) 153 | token1Hash := sha256.Sum256([]byte(calculateTOTP(otp, 0))) 154 | token2Hash := sha256.Sum256([]byte(calculateTOTP(otp, 1))) 155 | 156 | tokens[0] = hex.EncodeToString(token0Hash[:]) // 30 seconds ago token 157 | tokens[1] = hex.EncodeToString(token1Hash[:]) // current token 158 | tokens[2] = hex.EncodeToString(token2Hash[:]) // next 30 seconds token 159 | 160 | // if the current time token is valid then, no need to re-sync and return nil 161 | if tokens[1] == userToken { 162 | return nil 163 | } 164 | 165 | // if the 30 seconds ago token is valid then return nil, but re-synchronize 166 | if tokens[0] == userToken { 167 | otp.synchronizeCounter(-1) 168 | return nil 169 | } 170 | 171 | // if the let's say 30 seconds ago token is valid then return nil, but re-synchronize 172 | if tokens[2] == userToken { 173 | otp.synchronizeCounter(1) 174 | return nil 175 | } 176 | 177 | otp.totalVerificationFailures++ 178 | otp.lastVerificationTime = time.Now().UTC() // important to have it in UTC 179 | 180 | // if we got here everything is good 181 | return errors.New("Tokens mismatch.") 182 | } 183 | 184 | // Checks the time difference between the function call time and the parameter 185 | // if the difference of time is greater than BACKOFF_MINUTES it returns true, otherwise false 186 | func validBackoffTime(lastVerification time.Time) bool { 187 | diff := lastVerification.UTC().Add(backoff_minutes * time.Minute) 188 | return time.Now().UTC().After(diff) 189 | } 190 | 191 | // Basically, we define TOTP as TOTP = HOTP(K, T), where T is an integer 192 | // and represents the number of time steps between the initial counter 193 | // time T0 and the current Unix time. 194 | // T = (Current Unix time - T0) / X, where the 195 | // default floor function is used in the computation. 196 | // For example, with T0 = 0 and Time Step X = 30, T = 1 if the current 197 | // Unix time is 59 seconds, and T = 2 if the current Unix time is 198 | // 60 seconds. 199 | func (otp *Totp) incrementCounter(index int) { 200 | // Unix returns t as a Unix time, the number of seconds elapsed since January 1, 1970 UTC. 201 | counterOffset := time.Duration(index*otp.stepSize) * time.Second 202 | now := time.Now().UTC().Add(counterOffset).Unix() 203 | otp.counter = bigendian.ToUint64(increment(now, otp.stepSize)) 204 | } 205 | 206 | // Function which calculates the value of T (see rfc6238) 207 | func increment(ts int64, stepSize int) uint64 { 208 | T := float64(ts / int64(stepSize)) // TODO: improve this conversions 209 | n := convert.Round(T) // round T 210 | return n // convert n to little endian byte array 211 | } 212 | 213 | // Generates a new one time password with hmac-(HASH-FUNCTION) 214 | func (otp *Totp) OTP() (string, error) { 215 | 216 | // verify the proper initialization 217 | if err := totpHasBeenInitialized(otp); err != nil { 218 | return "", err 219 | } 220 | 221 | // it uses the index 0, meaning that it calculates the current one 222 | return calculateTOTP(otp, 0), nil 223 | } 224 | 225 | // Private function which calculates the OTP token based on the index offset 226 | // example: 1 * steps or -1 * steps 227 | func calculateTOTP(otp *Totp, index int) string { 228 | var h hash.Hash 229 | 230 | switch otp.hashFunction { 231 | case crypto.SHA256: 232 | h = hmac.New(sha256.New, otp.key) 233 | break 234 | case crypto.SHA512: 235 | h = hmac.New(sha512.New, otp.key) 236 | break 237 | default: 238 | h = hmac.New(sha1.New, otp.key) 239 | break 240 | 241 | } 242 | 243 | // set the counter to the current step based ont the current time 244 | // this is necessary to generate the proper OTP 245 | otp.incrementCounter(index) 246 | 247 | return calculateToken(otp.counter[:], otp.digits, h) 248 | 249 | } 250 | 251 | func truncateHash(hmac_result []byte, size int) int64 { 252 | offset := hmac_result[size-1] & 0xf 253 | bin_code := (uint32(hmac_result[offset])&0x7f)<<24 | 254 | (uint32(hmac_result[offset+1])&0xff)<<16 | 255 | (uint32(hmac_result[offset+2])&0xff)<<8 | 256 | (uint32(hmac_result[offset+3]) & 0xff) 257 | return int64(bin_code) 258 | } 259 | 260 | // this is the function which calculates the HTOP code 261 | func calculateToken(counter []byte, digits int, h hash.Hash) string { 262 | 263 | h.Write(counter) 264 | hashResult := h.Sum(nil) 265 | result := truncateHash(hashResult, h.Size()) 266 | 267 | mod := int32(result % int64(math.Pow10(digits))) 268 | 269 | fmtStr := fmt.Sprintf("%%0%dd", digits) 270 | 271 | return fmt.Sprintf(fmtStr, mod) 272 | } 273 | 274 | // Secret returns the underlying base32 encoded secret. 275 | // This should only be displayed the first time a user enables 2FA, 276 | // and should be transmitted over a secure connection. 277 | // Useful for supporting TOTP clients that don't support QR scanning. 278 | func (otp *Totp) Secret() string { 279 | return base32.StdEncoding.EncodeToString(otp.key) 280 | } 281 | 282 | // URL returns a suitable URL, such as for the Google Authenticator app 283 | // example: otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example 284 | func (otp *Totp) url() (string, error) { 285 | 286 | // verify the proper initialization 287 | if err := totpHasBeenInitialized(otp); err != nil { 288 | return "", err 289 | } 290 | 291 | secret := base32.StdEncoding.EncodeToString(otp.key) 292 | u := url.URL{} 293 | v := url.Values{} 294 | u.Scheme = "otpauth" 295 | u.Host = "totp" 296 | u.Path = otp.label() 297 | v.Add("secret", secret) 298 | v.Add("counter", fmt.Sprintf("%d", otp.getIntCounter())) 299 | v.Add("issuer", otp.issuer) 300 | v.Add("digits", strconv.Itoa(otp.digits)) 301 | v.Add("period", strconv.Itoa(otp.stepSize)) 302 | switch otp.hashFunction { 303 | case crypto.SHA256: 304 | v.Add("algorithm", "SHA256") 305 | break 306 | case crypto.SHA512: 307 | v.Add("algorithm", "SHA512") 308 | break 309 | default: 310 | v.Add("algorithm", "SHA1") 311 | break 312 | } 313 | u.RawQuery = v.Encode() 314 | return u.String(), nil 315 | } 316 | 317 | // QR generates a byte array containing QR code encoded PNG image, with level Q error correction, 318 | // needed for the client apps to generate tokens 319 | // The QR code should be displayed only the first time the user enabled the Two-Factor authentication. 320 | // The QR code contains the shared KEY between the server application and the client application, 321 | // therefore the QR code should be delivered via secure connection. 322 | func (otp *Totp) QR() ([]byte, error) { 323 | 324 | // get the URL 325 | u, err := otp.url() 326 | 327 | // check for errors during initialization 328 | // this is already done on the URL method 329 | if err != nil { 330 | return nil, err 331 | } 332 | code, err := qr.Encode(u, qr.Q) 333 | if err != nil { 334 | return nil, err 335 | } 336 | return code.PNG(), nil 337 | } 338 | 339 | // ToBytes serialises a TOTP object in a byte array 340 | // Sizes: 4 4 N 8 4 4 N 4 N 4 4 4 8 4 341 | // Format: |total_bytes|key_size|key|counter|digits|issuer_size|issuer|account_size|account|steps|offset|total_failures|verification_time|hashFunction_type| 342 | // hashFunction_type: 0 = SHA1; 1 = SHA256; 2 = SHA512 343 | // The data is encrypted using the cryptoengine library (which is a wrapper around the golang NaCl library) 344 | // TODO: 345 | // 1- improve sizes. For instance the hashFunction_type could be a short. 346 | func (otp *Totp) ToBytes() ([]byte, error) { 347 | 348 | // check Totp initialization 349 | if err := totpHasBeenInitialized(otp); err != nil { 350 | return nil, err 351 | } 352 | 353 | var buffer bytes.Buffer 354 | 355 | // calculate the length of the key and create its byte representation 356 | keySize := len(otp.key) 357 | keySizeBytes := bigendian.ToInt(keySize) //bigEndianInt(keySize) 358 | 359 | // calculate the length of the issuer and create its byte representation 360 | issuerSize := len(otp.issuer) 361 | issuerSizeBytes := bigendian.ToInt(issuerSize) 362 | 363 | // calculate the length of the account and create its byte representation 364 | accountSize := len(otp.account) 365 | accountSizeBytes := bigendian.ToInt(accountSize) 366 | 367 | totalSize := 4 + 4 + keySize + 8 + 4 + 4 + issuerSize + 4 + accountSize + 4 + 4 + 4 + 8 + 4 368 | totalSizeBytes := bigendian.ToInt(totalSize) 369 | 370 | // at this point we are ready to write the data to the byte buffer 371 | // total size 372 | if _, err := buffer.Write(totalSizeBytes[:]); err != nil { 373 | return nil, err 374 | } 375 | 376 | // key 377 | if _, err := buffer.Write(keySizeBytes[:]); err != nil { 378 | return nil, err 379 | } 380 | if _, err := buffer.Write(otp.key); err != nil { 381 | return nil, err 382 | } 383 | 384 | // counter 385 | counterBytes := bigendian.ToUint64(otp.getIntCounter()) 386 | if _, err := buffer.Write(counterBytes[:]); err != nil { 387 | return nil, err 388 | } 389 | 390 | // digits 391 | digitBytes := bigendian.ToInt(otp.digits) 392 | if _, err := buffer.Write(digitBytes[:]); err != nil { 393 | return nil, err 394 | } 395 | 396 | // issuer 397 | if _, err := buffer.Write(issuerSizeBytes[:]); err != nil { 398 | return nil, err 399 | } 400 | if _, err := buffer.WriteString(otp.issuer); err != nil { 401 | return nil, err 402 | } 403 | 404 | // account 405 | if _, err := buffer.Write(accountSizeBytes[:]); err != nil { 406 | return nil, err 407 | } 408 | if _, err := buffer.WriteString(otp.account); err != nil { 409 | return nil, err 410 | } 411 | 412 | // steps 413 | stepsBytes := bigendian.ToInt(otp.stepSize) 414 | if _, err := buffer.Write(stepsBytes[:]); err != nil { 415 | return nil, err 416 | } 417 | 418 | // offset 419 | offsetBytes := bigendian.ToInt(otp.clientOffset) 420 | if _, err := buffer.Write(offsetBytes[:]); err != nil { 421 | return nil, err 422 | } 423 | 424 | // total_failures 425 | totalFailuresBytes := bigendian.ToInt(otp.totalVerificationFailures) 426 | if _, err := buffer.Write(totalFailuresBytes[:]); err != nil { 427 | return nil, err 428 | } 429 | 430 | // last verification time 431 | verificationTimeBytes := bigendian.ToUint64(uint64(otp.lastVerificationTime.Unix())) 432 | if _, err := buffer.Write(verificationTimeBytes[:]); err != nil { 433 | return nil, err 434 | } 435 | 436 | // has_function_type 437 | switch otp.hashFunction { 438 | case crypto.SHA256: 439 | sha256Bytes := bigendian.ToInt(1) 440 | if _, err := buffer.Write(sha256Bytes[:]); err != nil { 441 | return nil, err 442 | } 443 | break 444 | case crypto.SHA512: 445 | sha512Bytes := bigendian.ToInt(2) 446 | if _, err := buffer.Write(sha512Bytes[:]); err != nil { 447 | return nil, err 448 | } 449 | break 450 | default: 451 | sha1Bytes := bigendian.ToInt(0) 452 | if _, err := buffer.Write(sha1Bytes[:]); err != nil { 453 | return nil, err 454 | } 455 | } 456 | 457 | // encrypt the TOTP bytes 458 | engine, err := cryptoengine.InitCryptoEngine(otp.issuer) 459 | if err != nil { 460 | return nil, err 461 | } 462 | 463 | // init the message to be encrypted 464 | message, err := cryptoengine.NewMessage(buffer.String(), message_type) 465 | if err != nil { 466 | return nil, err 467 | } 468 | 469 | // encrypt it 470 | encryptedMessage, err := engine.NewEncryptedMessage(message) 471 | if err != nil { 472 | return nil, err 473 | } 474 | 475 | return encryptedMessage.ToBytes() 476 | 477 | } 478 | 479 | // TOTPFromBytes converts a byte array to a totp object 480 | // it stores the state of the TOTP object, like the key, the current counter, the client offset, 481 | // the total amount of verification failures and the last time a verification happened 482 | func TOTPFromBytes(encryptedMessage []byte, issuer string) (*Totp, error) { 483 | 484 | // init the cryptoengine 485 | engine, err := cryptoengine.InitCryptoEngine(issuer) 486 | if err != nil { 487 | return nil, err 488 | } 489 | 490 | // decrypt the message 491 | data, err := engine.Decrypt(encryptedMessage) 492 | if err != nil { 493 | return nil, err 494 | } 495 | 496 | // new reader 497 | reader := bytes.NewReader([]byte(data.Text)) 498 | 499 | // otp object 500 | otp := new(Totp) 501 | 502 | // get the length 503 | length := make([]byte, 4) 504 | _, err = reader.Read(length) // read the 4 bytes for the total length 505 | if err != nil && err != io.EOF { 506 | return otp, err 507 | } 508 | 509 | totalSize := bigendian.FromInt([4]byte{length[0], length[1], length[2], length[3]}) 510 | buffer := make([]byte, totalSize-4) 511 | _, err = reader.Read(buffer) 512 | if err != nil && err != io.EOF { 513 | return otp, err 514 | } 515 | 516 | // skip the total bytes size 517 | startOffset := 0 518 | // read key size 519 | endOffset := startOffset + 4 520 | keyBytes := buffer[startOffset:endOffset] 521 | keySize := bigendian.FromInt([4]byte{keyBytes[0], keyBytes[1], keyBytes[2], keyBytes[3]}) 522 | 523 | // read the key 524 | startOffset = endOffset 525 | endOffset = startOffset + keySize 526 | otp.key = buffer[startOffset:endOffset] 527 | 528 | // read the counter 529 | startOffset = endOffset 530 | endOffset = startOffset + 8 531 | b := buffer[startOffset:endOffset] 532 | otp.counter = [8]byte{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]} 533 | 534 | // read the digits 535 | startOffset = endOffset 536 | endOffset = startOffset + 4 537 | b = buffer[startOffset:endOffset] 538 | otp.digits = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) // 539 | 540 | // read the issuer size 541 | startOffset = endOffset 542 | endOffset = startOffset + 4 543 | b = buffer[startOffset:endOffset] 544 | issuerSize := bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) 545 | 546 | // read the issuer string 547 | startOffset = endOffset 548 | endOffset = startOffset + issuerSize 549 | otp.issuer = string(buffer[startOffset:endOffset]) 550 | 551 | // read the account size 552 | startOffset = endOffset 553 | endOffset = startOffset + 4 554 | b = buffer[startOffset:endOffset] 555 | accountSize := bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) 556 | 557 | // read the account string 558 | startOffset = endOffset 559 | endOffset = startOffset + accountSize 560 | otp.account = string(buffer[startOffset:endOffset]) 561 | 562 | // read the steps 563 | startOffset = endOffset 564 | endOffset = startOffset + 4 565 | b = buffer[startOffset:endOffset] 566 | otp.stepSize = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) 567 | 568 | // read the offset 569 | startOffset = endOffset 570 | endOffset = startOffset + 4 571 | b = buffer[startOffset:endOffset] 572 | otp.clientOffset = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) 573 | 574 | // read the total failures 575 | startOffset = endOffset 576 | endOffset = startOffset + 4 577 | b = buffer[startOffset:endOffset] 578 | otp.totalVerificationFailures = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) 579 | 580 | // read the offset 581 | startOffset = endOffset 582 | endOffset = startOffset + 8 583 | b = buffer[startOffset:endOffset] 584 | ts := bigendian.FromUint64([8]byte{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]}) 585 | otp.lastVerificationTime = time.Unix(int64(ts), 0) 586 | 587 | // read the hash type 588 | startOffset = endOffset 589 | endOffset = startOffset + 4 590 | b = buffer[startOffset:endOffset] 591 | hashType := bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) 592 | 593 | switch hashType { 594 | case 1: 595 | otp.hashFunction = crypto.SHA256 596 | break 597 | case 2: 598 | otp.hashFunction = crypto.SHA512 599 | break 600 | default: 601 | otp.hashFunction = crypto.SHA1 602 | } 603 | 604 | return otp, err 605 | } 606 | 607 | // this method checks the proper initialization of the Totp object 608 | func totpHasBeenInitialized(otp *Totp) error { 609 | if otp == nil || otp.key == nil || len(otp.key) == 0 { 610 | return initializationFailedError 611 | } 612 | return nil 613 | } 614 | -------------------------------------------------------------------------------- /totp_test.go: -------------------------------------------------------------------------------- 1 | package twofactor 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/hmac" 7 | "crypto/sha1" 8 | "crypto/sha256" 9 | "crypto/sha512" 10 | "encoding/base64" 11 | "encoding/hex" 12 | "net/url" 13 | "testing" 14 | "time" 15 | 16 | "github.com/sec51/convert/bigendian" 17 | ) 18 | 19 | var sha1KeyHex = "3132333435363738393031323334353637383930" 20 | var sha256KeyHex = "3132333435363738393031323334353637383930313233343536373839303132" 21 | var sha512KeyHex = "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334" 22 | 23 | var sha1TestData = []string{ 24 | "94287082", 25 | "07081804", 26 | "14050471", 27 | "89005924", 28 | "69279037", 29 | "65353130", 30 | } 31 | 32 | var sha256TestData = []string{ 33 | "46119246", 34 | "68084774", 35 | "67062674", 36 | "91819424", 37 | "90698825", 38 | "77737706", 39 | } 40 | 41 | var sha512TestData = []string{ 42 | "90693936", 43 | "25091201", 44 | "99943326", 45 | "93441116", 46 | "38618901", 47 | "47863826", 48 | } 49 | 50 | var timeCounters = []int64{ 51 | int64(59), // 1970-01-01 00:00:59 52 | int64(1111111109), // 2005-03-18 01:58:29 53 | int64(1111111111), // 2005-03-18 01:58:31 54 | int64(1234567890), // 2009-02-13 23:31:30 55 | int64(2000000000), // 2033-05-18 03:33:20 56 | int64(20000000000), // 2603-10-11 11:33:20 57 | } 58 | 59 | func checkError(t *testing.T, err error) { 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | } 64 | 65 | func TestTOTP(t *testing.T) { 66 | 67 | keySha1, err := hex.DecodeString(sha1KeyHex) 68 | checkError(t, err) 69 | 70 | keySha256, err := hex.DecodeString(sha256KeyHex) 71 | checkError(t, err) 72 | 73 | keySha512, err := hex.DecodeString(sha512KeyHex) 74 | checkError(t, err) 75 | 76 | // create the OTP 77 | otp := new(Totp) 78 | otp.digits = 8 79 | otp.issuer = "Sec51" 80 | otp.account = "no-reply@sec51.com" 81 | 82 | // Test SHA1 83 | otp.key = keySha1 84 | for index, ts := range timeCounters { 85 | counter := increment(ts, 30) 86 | otp.counter = bigendian.ToUint64(counter) 87 | hash := hmac.New(sha1.New, otp.key) 88 | token := calculateToken(otp.counter[:], otp.digits, hash) 89 | expected := sha1TestData[index] 90 | if token != expected { 91 | t.Errorf("SHA1 test data, token mismatch. Got %s, expected %s\n", token, expected) 92 | } 93 | } 94 | 95 | // Test SHA256 96 | otp.key = keySha256 97 | for index, ts := range timeCounters { 98 | counter := increment(ts, 30) 99 | otp.counter = bigendian.ToUint64(counter) 100 | hash := hmac.New(sha256.New, otp.key) 101 | token := calculateToken(otp.counter[:], otp.digits, hash) 102 | expected := sha256TestData[index] 103 | if token != expected { 104 | t.Errorf("SHA256 test data, token mismatch. Got %s, expected %s\n", token, expected) 105 | } 106 | } 107 | 108 | // Test SHA512 109 | otp.key = keySha512 110 | for index, ts := range timeCounters { 111 | counter := increment(ts, 30) 112 | otp.counter = bigendian.ToUint64(counter) 113 | hash := hmac.New(sha512.New, otp.key) 114 | token := calculateToken(otp.counter[:], otp.digits, hash) 115 | expected := sha512TestData[index] 116 | if token != expected { 117 | t.Errorf("SHA512 test data, token mismatch. Got %s, expected %s\n", token, expected) 118 | } 119 | } 120 | 121 | } 122 | 123 | func TestVerificationFailures(t *testing.T) { 124 | 125 | otp, err := NewTOTP("info@sec51.com", "Sec51", crypto.SHA1, 7) 126 | //checkError(t, err) 127 | if err != nil { 128 | t.Fatal(err) 129 | } 130 | 131 | // generate a new token 132 | expectedToken, err := otp.OTP() 133 | if err != nil { 134 | t.Fatal(err) 135 | } 136 | 137 | //verify the new token 138 | if err := otp.Validate(expectedToken); err != nil { 139 | t.Fatal(err) 140 | } 141 | 142 | // verify the wrong token for 10 times and check the internal counters values 143 | for i := 0; i < 10; i++ { 144 | if err := otp.Validate("1234567"); err == nil { 145 | t.Fatal(err) 146 | } 147 | } 148 | 149 | if otp.totalVerificationFailures != 3 { 150 | t.Errorf("Expected 3 verification failures, instead we've got %d\n", otp.totalVerificationFailures) 151 | } 152 | 153 | // at this point we crossed the max failures, therefore it should always return an error 154 | for i := 0; i < 10; i++ { 155 | if err := otp.Validate(expectedToken); err == nil { 156 | t.Fatal(err) 157 | } 158 | } 159 | 160 | // test the validBackoffTime function 161 | if validBackoffTime(otp.lastVerificationTime) { 162 | t.Error("validBackoffTime should return false") 163 | } 164 | 165 | // serialize and deserialize the object and verify again 166 | data, err := otp.ToBytes() 167 | if err != nil { 168 | t.Fatal(err) 169 | } 170 | 171 | restoredOtp, err := TOTPFromBytes(data, otp.issuer) 172 | if err != nil { 173 | t.Fatal(err) 174 | } 175 | 176 | // make sure the fields are the same after parsing the token from bytes 177 | if otp.label() != restoredOtp.label() { 178 | t.Error("Label mismatch between in memory OTP and byte parsed OTP") 179 | } 180 | 181 | // test the validBackoffTime function 182 | if validBackoffTime(restoredOtp.lastVerificationTime) { 183 | t.Error("validBackoffTime should return false") 184 | } 185 | 186 | // set the lastVerificationTime back in the past. 187 | // it should at this point pass 188 | back10Minutes := time.Duration(-10) * time.Minute 189 | otp.lastVerificationTime = time.Now().UTC().Add(back10Minutes) 190 | 191 | // test the validBackoffTime function 192 | if !validBackoffTime(otp.lastVerificationTime) { 193 | t.Error("validBackoffTime should return true") 194 | } 195 | 196 | for i := 0; i < 10; i++ { 197 | if err := otp.Validate(expectedToken); err != nil { 198 | t.Fatal(err) 199 | } 200 | 201 | if i == 0 { 202 | // at this point the max failure counter should have been reset to zero 203 | if otp.totalVerificationFailures != 0 { 204 | t.Errorf("totalVerificationFailures counter not reset to zero. We've got: %d\n", otp.totalVerificationFailures) 205 | } 206 | } 207 | } 208 | 209 | } 210 | 211 | func TestIncrementCounter(t *testing.T) { 212 | 213 | ts := int64(1438601387) 214 | unixTime := time.Unix(ts, 0).UTC() 215 | // DEBUG 216 | // fmt.Println(time.Unix(ts, 0).UTC().Format(time.RFC1123)) 217 | result := increment(unixTime.Unix(), 30) 218 | expected := uint64(47953379) 219 | if result != expected { 220 | t.Fatal("Error incrementing counter") 221 | } 222 | 223 | } 224 | 225 | func TestSerialization(t *testing.T) { 226 | // create a new TOTP 227 | otp, err := NewTOTP("info@sec51.com", "Sec51", crypto.SHA512, 8) 228 | if err != nil { 229 | t.Fatal(err) 230 | } 231 | 232 | // set some properties to a value different than the default 233 | otp.totalVerificationFailures = 2 234 | otp.stepSize = 27 235 | otp.lastVerificationTime = time.Now().UTC() 236 | otp.clientOffset = 1 237 | 238 | // Serialize it to bytes 239 | otpData, err := otp.ToBytes() 240 | if err != nil { 241 | t.Fatal(err) 242 | } 243 | 244 | // Convert it back from bytes to TOTP 245 | deserializedOTP, err := TOTPFromBytes(otpData, otp.issuer) 246 | if err != nil { 247 | t.Fatal(err) 248 | } 249 | 250 | deserializedOTPData, err := deserializedOTP.ToBytes() 251 | if err != nil { 252 | t.Fatal(err) 253 | } 254 | 255 | if deserializedOTP == nil { 256 | t.Error("Could not deserialize back the TOTP object from bytes") 257 | } 258 | 259 | if bytes.Compare(deserializedOTP.key, otp.key) != 0 { 260 | t.Error("Deserialized digits property differ from original TOTP") 261 | } 262 | 263 | if deserializedOTP.digits != otp.digits { 264 | t.Error("Deserialized digits property differ from original TOTP") 265 | } 266 | 267 | if deserializedOTP.totalVerificationFailures != otp.totalVerificationFailures { 268 | t.Error("Deserialized totalVerificationFailures property differ from original TOTP") 269 | } 270 | 271 | if deserializedOTP.stepSize != otp.stepSize { 272 | t.Error("Deserialized stepSize property differ from original TOTP") 273 | } 274 | 275 | if deserializedOTP.lastVerificationTime.Unix() != otp.lastVerificationTime.Unix() { 276 | t.Error("Deserialized lastVerificationTime property differ from original TOTP") 277 | } 278 | 279 | if deserializedOTP.getIntCounter() != otp.getIntCounter() { 280 | t.Error("Deserialized counter property differ from original TOTP") 281 | } 282 | 283 | if deserializedOTP.clientOffset != otp.clientOffset { 284 | t.Error("Deserialized clientOffset property differ from original TOTP") 285 | } 286 | 287 | if deserializedOTP.account != otp.account { 288 | t.Error("Deserialized account property differ from original TOTP") 289 | } 290 | 291 | if deserializedOTP.issuer != otp.issuer { 292 | t.Error("Deserialized issuer property differ from original TOTP") 293 | } 294 | 295 | deserializedToken, err := deserializedOTP.OTP() 296 | if err != nil { 297 | t.Error(err) 298 | } 299 | token, err := otp.OTP() 300 | if err != nil { 301 | t.Error(err) 302 | } 303 | if deserializedToken != token { 304 | t.Error("Deserialized OTP token property differ from original TOTP") 305 | } 306 | 307 | if deserializedOTP.hashFunction != otp.hashFunction { 308 | t.Error("Deserialized hash property differ from original TOTP") 309 | } 310 | 311 | deserializedUrl, err := deserializedOTP.url() 312 | if err != nil { 313 | t.Error(err) 314 | } 315 | 316 | otpdUrl, err := otp.url() 317 | if err != nil { 318 | t.Error(err) 319 | } 320 | if deserializedUrl != otpdUrl { 321 | t.Error("Deserialized URL property differ from original TOTP") 322 | } 323 | 324 | if deserializedOTP.label() != otp.label() { 325 | t.Error("Deserialized Label property differ from original TOTP") 326 | } 327 | 328 | if base64.StdEncoding.EncodeToString(otpData) != base64.StdEncoding.EncodeToString(deserializedOTPData) { 329 | t.Error("Problems encoding TOTP to base64") 330 | } 331 | 332 | label, err := url.QueryUnescape(otp.label()) 333 | if err != nil { 334 | t.Fatal(err) 335 | } 336 | 337 | if label != "Sec51:info@sec51.com" { 338 | t.Error("Creation of TOTP Label failed") 339 | } 340 | 341 | } 342 | 343 | func TestProperInitialization(t *testing.T) { 344 | otp := Totp{} 345 | if _, err := otp.url(); err == nil { 346 | t.Fatal("Totp is not properly initialized and the method did not catch it") 347 | } 348 | } 349 | 350 | func TestCounterSynchronization(t *testing.T) { 351 | 352 | // create totp 353 | otp, err := NewTOTP("info@sec51.com", "Sec51", crypto.SHA512, 8) 354 | if err != nil { 355 | t.Fatal(err) 356 | } 357 | 358 | token0 := calculateTOTP(otp, 0) 359 | if err != nil { 360 | t.Fatal(err) 361 | } 362 | 363 | token_1 := calculateTOTP(otp, -1) 364 | if err != nil { 365 | t.Fatal(err) 366 | } 367 | 368 | token1 := calculateTOTP(otp, 1) 369 | if err != nil { 370 | t.Fatal(err) 371 | } 372 | 373 | err = otp.Validate(token0) 374 | if err != nil { 375 | t.Error(err) 376 | } 377 | // check the values 378 | if otp.clientOffset != 0 { 379 | t.Errorf("Client offset should be 0, instead we've got %d\n", otp.clientOffset) 380 | } 381 | 382 | err = otp.Validate(token_1) 383 | if err != nil { 384 | t.Error(err) 385 | } 386 | // check the values 387 | if otp.clientOffset != -1 { 388 | t.Errorf("Client offset should be -1, instead we've got %d\n", otp.clientOffset) 389 | } 390 | 391 | err = otp.Validate(token1) 392 | if err != nil { 393 | t.Error(err) 394 | } 395 | // check the values 396 | if otp.clientOffset != 1 { 397 | t.Errorf("Client offset should be 0, instead we've got %d\n", otp.clientOffset) 398 | } 399 | 400 | } 401 | --------------------------------------------------------------------------------