├── README.md ├── examples ├── FullExample_WiFi_NTP_OTP │ └── FullExample_WiFi_NTP_OTP.ino └── GetCode │ └── GetCode.ino ├── keywords.txt ├── library.properties └── src ├── TOTP.cpp ├── TOTP.h ├── sha1.cpp └── sha1.h /README.md: -------------------------------------------------------------------------------- 1 | Arduino TOTP Library 2 | ==================== 3 | 4 | Library to generate Time-based One-Time Passwords. 5 | 6 | Implements the Time-based One-Time Password algorithm specified in [RFC 6238](https://tools.ietf.org/html/rfc6238). Supports different time steps and it's compatible with tokens that uses the same standard (including software ones, like the Google Authenticator app). 7 | 8 | 9 | Installation & usage: 10 | -------------------- 11 | Install the library using the Library Manager or manually in the \libraries folder of your IDE. 12 | This library requires the [Cryptosuite library](https://github.com/maniacbug/Cryptosuite) by maniacbug. 13 | 14 | First, store your private key into an array: 15 | ```c++ 16 | uint8_t hmacKey[] = {0x4d, 0x79, 0x4c, 0x65, 0x67, 0x6f, 0x44, 0x6f, 0x6f, 0x72}; 17 | ``` 18 | Then create a new instance of the TOTP class using one of the two available constructors: 19 | ```c++ 20 | TOTP(uint8_t* hmacKey, int keyLength); 21 | TOTP(uint8_t* hmacKey, int keyLength, int timeStep); 22 | ``` 23 | The first assumes a timeStep of 30 seconds, value used for example by the Google Authenticator app. 24 | 25 | Two methods are available to get a TOTP passcode: 26 | ```c++ 27 | char* getCode(long timeStamp); 28 | char* getCodeFromSteps(long steps); 29 | ``` 30 | The first accept a unix timestamp (number of seconds since Epoch), the second the number of "steps" since Epoch (that is seconds / timeStep) and it's useful to get a pool of values. 31 | 32 | A demo project: 33 | --------------- 34 | 35 | http://www.lucadentella.it/2013/09/14/serratura-otp/ 36 | 37 | 38 | Thanks to: 39 | ---------- 40 | 41 | * Jose Damico, https://github.com/damico/ARDUINO-OATH-TOKEN 42 | * Peter Knight, https://github.com/Cathedrow/Cryptosuite 43 | * Maniacbug, https://github.com/maniacbug/Cryptosuite 44 | -------------------------------------------------------------------------------- /examples/FullExample_WiFi_NTP_OTP/FullExample_WiFi_NTP_OTP.ino: -------------------------------------------------------------------------------- 1 | // TOTP DEMO, v1.0 2 | // 3 | // Requires a WiFi-capable board (for example esp32) 4 | // and NTPClient library: https://github.com/arduino-libraries/NTPClient 5 | // 6 | // Change the wifi settings and enter your hmacKey 7 | // 8 | // To generate the hmacKey and initialize the smartphone app 9 | // you can use my tool: http://www.lucadentella.it/OTP 10 | // 11 | // Tested with Arduino 1.8.12, NTPClient 3.2.0 and esp32 1.0.4 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | // change the following settings according to your WiFi network 19 | char ssid[] = "mySSID"; 20 | char password[] = "myPASSWORD"; 21 | 22 | // enter your hmacKey (10 digits) 23 | uint8_t hmacKey[] = {0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x6b, 0x65, 0x79, 0x30}; 24 | 25 | WiFiUDP ntpUDP; 26 | NTPClient timeClient(ntpUDP); 27 | TOTP totp = TOTP(hmacKey, 10); 28 | 29 | String totpCode = String(""); 30 | 31 | void setup() { 32 | 33 | Serial.begin(9600); 34 | while (!Serial); 35 | 36 | Serial.println("TOTP demo"); 37 | Serial.println(); 38 | 39 | // connect to the WiFi network 40 | WiFi.begin(ssid, password); 41 | while (WiFi.status() != WL_CONNECTED) { 42 | delay(1000); 43 | Serial.println("Establishing connection to WiFi..."); 44 | } 45 | Serial.print("Connected to WiFi with IP: "); 46 | Serial.println(WiFi.localIP()); 47 | Serial.println(); 48 | 49 | // start the NTP client 50 | timeClient.begin(); 51 | Serial.println("NTP client started"); 52 | Serial.println(); 53 | } 54 | 55 | void loop() { 56 | 57 | // update the time 58 | timeClient.update(); 59 | 60 | // generate the TOTP code and, if different from the previous one, print to screen 61 | String newCode = String(totp.getCode(timeClient.getEpochTime())); 62 | if(totpCode!= newCode) { 63 | totpCode = String(newCode); 64 | Serial.print("TOTP code: "); 65 | Serial.println(newCode); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/GetCode/GetCode.ino: -------------------------------------------------------------------------------- 1 | // GetCode.ino 2 | // 3 | // Basic example for the TOTP library 4 | // 5 | // This example uses the opensource SwRTC library as a software real-time clock 6 | // you can download from https://github.com/leomil72/swRTC 7 | // for real implementation it's suggested the use of an hardware RTC 8 | 9 | #include "sha1.h" 10 | #include "TOTP.h" 11 | #include "swRTC.h" 12 | 13 | // The shared secret is MyLegoDoor 14 | uint8_t hmacKey[] = {0x4d, 0x79, 0x4c, 0x65, 0x67, 0x6f, 0x44, 0x6f, 0x6f, 0x72}; 15 | 16 | TOTP totp = TOTP(hmacKey, 10); 17 | swRTC rtc; 18 | char code[7]; 19 | 20 | 21 | void setup() { 22 | 23 | Serial.begin(9600); 24 | rtc.stopRTC(); 25 | 26 | // Adjust the following values to match the current date and time 27 | // and power on Arduino at the time set (use GMT timezone!) 28 | rtc.setDate(27, 8, 2013); 29 | rtc.setTime(21, 25, 00); 30 | 31 | rtc.startRTC(); 32 | } 33 | 34 | void loop() { 35 | 36 | long GMT = rtc.getTimestamp(); 37 | char* newCode = totp.getCode(GMT); 38 | if(strcmp(code, newCode) != 0) { 39 | strcpy(code, newCode); 40 | Serial.println(code); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For TOTP 3 | ####################################### 4 | # Datatypes (KEYWORD1) 5 | ####################################### 6 | 7 | TOTP KEYWORD1 8 | 9 | ####################################### 10 | # Methods and Functions (KEYWORD2) 11 | ####################################### 12 | 13 | getCode KEYWORD2 14 | getCodeFromSteps KEYWORD2 15 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=TOTP library 2 | version=1.1.0 3 | author=Luca Dentella 4 | maintainer=Luca Dentella 5 | sentence=Library to generate Time-based One-Time Passwords 6 | paragraph=Implements the Time-based One-Time Password algorithm specified in RFC 6238. Supports different time steps and it's compatible with tokens that uses the same standard (including software ones, like the Google Authenticator app). 7 | category=Other 8 | url=https://github.com/lucadentella/TOTP-Arduino 9 | architectures=* -------------------------------------------------------------------------------- /src/TOTP.cpp: -------------------------------------------------------------------------------- 1 | // OpenAuthentication Time-based One-time Password Algorithm (RFC 6238) 2 | // For the complete description of the algorithm see 3 | // http://tools.ietf.org/html/rfc4226#section-5.3 4 | // 5 | // Luca Dentella (http://www.lucadentella.it) 6 | 7 | #include "TOTP.h" 8 | #include "sha1.h" 9 | 10 | // Init the library with the private key, its length and the timeStep duration 11 | TOTP::TOTP(uint8_t* hmacKey, int keyLength, int timeStep) { 12 | 13 | _hmacKey = hmacKey; 14 | _keyLength = keyLength; 15 | _timeStep = timeStep; 16 | }; 17 | 18 | // Init the library with the private key, its length and a time step of 30sec (default for Google Authenticator) 19 | TOTP::TOTP(uint8_t* hmacKey, int keyLength) { 20 | 21 | _hmacKey = hmacKey; 22 | _keyLength = keyLength; 23 | _timeStep = 30; 24 | }; 25 | 26 | // Generate a code, using the timestamp provided 27 | char* TOTP::getCode(long timeStamp) { 28 | 29 | long steps = timeStamp / _timeStep; 30 | return getCodeFromSteps(steps); 31 | } 32 | 33 | // Generate a code, using the number of steps provided 34 | char* TOTP::getCodeFromSteps(long steps) { 35 | 36 | // STEP 0, map the number of steps in a 8-bytes array (counter value) 37 | _byteArray[0] = 0x00; 38 | _byteArray[1] = 0x00; 39 | _byteArray[2] = 0x00; 40 | _byteArray[3] = 0x00; 41 | _byteArray[4] = (int)((steps >> 24) & 0xFF); 42 | _byteArray[5] = (int)((steps >> 16) & 0xFF); 43 | _byteArray[6] = (int)((steps >> 8) & 0XFF); 44 | _byteArray[7] = (int)((steps & 0XFF)); 45 | 46 | // STEP 1, get the HMAC-SHA1 hash from counter and key 47 | Sha1.initHmac(_hmacKey, _keyLength); 48 | Sha1.write(_byteArray, 8); 49 | _hash = Sha1.resultHmac(); 50 | 51 | // STEP 2, apply dynamic truncation to obtain a 4-bytes string 52 | _offset = _hash[20 - 1] & 0xF; 53 | _truncatedHash = 0; 54 | for (int j = 0; j < 4; ++j) { 55 | _truncatedHash <<= 8; 56 | _truncatedHash |= _hash[_offset + j]; 57 | } 58 | 59 | // STEP 3, compute the OTP value 60 | _truncatedHash &= 0x7FFFFFFF; 61 | _truncatedHash %= 1000000; 62 | 63 | // convert the value in string, with leading zeroes 64 | sprintf(_code, "%06ld", _truncatedHash); 65 | return _code; 66 | } 67 | -------------------------------------------------------------------------------- /src/TOTP.h: -------------------------------------------------------------------------------- 1 | // OpenAuthentication Time-based One-time Password Algorithm (RFC 6238) 2 | // Arduino Library 3 | // 4 | // Luca Dentella (http://www.lucadentella.it) 5 | 6 | #include "Arduino.h" 7 | 8 | #ifndef _TOTP_H 9 | #define _TOTP_H 10 | 11 | 12 | class TOTP { 13 | 14 | public: 15 | 16 | TOTP(uint8_t* hmacKey, int keyLength); 17 | TOTP(uint8_t* hmacKey, int keyLength, int timeStep); 18 | char* getCode(long timeStamp); 19 | char* getCodeFromSteps(long steps); 20 | 21 | private: 22 | 23 | uint8_t* _hmacKey; 24 | int _keyLength; 25 | int _timeStep; 26 | uint8_t _byteArray[8]; 27 | uint8_t* _hash; 28 | int _offset; 29 | long _truncatedHash; 30 | char _code[7]; 31 | }; 32 | 33 | #endif -------------------------------------------------------------------------------- /src/sha1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "sha1.h" 3 | 4 | #define SHA1_K0 0x5a827999 5 | #define SHA1_K20 0x6ed9eba1 6 | #define SHA1_K40 0x8f1bbcdc 7 | #define SHA1_K60 0xca62c1d6 8 | 9 | const uint8_t sha1InitState[] = { 10 | 0x01,0x23,0x45,0x67, // H0 11 | 0x89,0xab,0xcd,0xef, // H1 12 | 0xfe,0xdc,0xba,0x98, // H2 13 | 0x76,0x54,0x32,0x10, // H3 14 | 0xf0,0xe1,0xd2,0xc3 // H4 15 | }; 16 | 17 | void Sha1Class::init(void) { 18 | memcpy(state.b,sha1InitState,HASH_LENGTH); 19 | byteCount = 0; 20 | bufferOffset = 0; 21 | } 22 | 23 | uint32_t Sha1Class::rol32(uint32_t number, uint8_t bits) { 24 | return ((number << bits) | (number >> (32-bits))); 25 | } 26 | 27 | void Sha1Class::hashBlock() { 28 | uint8_t i; 29 | uint32_t a,b,c,d,e,t; 30 | 31 | a=state.w[0]; 32 | b=state.w[1]; 33 | c=state.w[2]; 34 | d=state.w[3]; 35 | e=state.w[4]; 36 | for (i=0; i<80; i++) { 37 | if (i>=16) { 38 | t = buffer.w[(i+13)&15] ^ buffer.w[(i+8)&15] ^ buffer.w[(i+2)&15] ^ buffer.w[i&15]; 39 | buffer.w[i&15] = rol32(t,1); 40 | } 41 | if (i<20) { 42 | t = (d ^ (b & (c ^ d))) + SHA1_K0; 43 | } else if (i<40) { 44 | t = (b ^ c ^ d) + SHA1_K20; 45 | } else if (i<60) { 46 | t = ((b & c) | (d & (b | c))) + SHA1_K40; 47 | } else { 48 | t = (b ^ c ^ d) + SHA1_K60; 49 | } 50 | t+=rol32(a,5) + e + buffer.w[i&15]; 51 | e=d; 52 | d=c; 53 | c=rol32(b,30); 54 | b=a; 55 | a=t; 56 | } 57 | state.w[0] += a; 58 | state.w[1] += b; 59 | state.w[2] += c; 60 | state.w[3] += d; 61 | state.w[4] += e; 62 | } 63 | 64 | void Sha1Class::addUncounted(uint8_t data) { 65 | buffer.b[bufferOffset ^ 3] = data; 66 | bufferOffset++; 67 | if (bufferOffset == BLOCK_LENGTH) { 68 | hashBlock(); 69 | bufferOffset = 0; 70 | } 71 | } 72 | 73 | __WRITE_RESULT Sha1Class::write(uint8_t data) { 74 | ++byteCount; 75 | addUncounted(data); 76 | 77 | __WRITE_RETURN(1); 78 | } 79 | 80 | void Sha1Class::pad() { 81 | // Implement SHA-1 padding (fips180-2 §5.1.1) 82 | 83 | // Pad with 0x80 followed by 0x00 until the end of the block 84 | addUncounted(0x80); 85 | while (bufferOffset != 56) addUncounted(0x00); 86 | 87 | // Append length in the last 8 bytes 88 | addUncounted(0); // We're only using 32 bit lengths 89 | addUncounted(0); // But SHA-1 supports 64 bit lengths 90 | addUncounted(0); // So zero pad the top bits 91 | addUncounted(byteCount >> 29); // Shifting to multiply by 8 92 | addUncounted(byteCount >> 21); // as SHA-1 supports bitstreams as well as 93 | addUncounted(byteCount >> 13); // byte. 94 | addUncounted(byteCount >> 5); 95 | addUncounted(byteCount << 3); 96 | } 97 | 98 | 99 | uint8_t* Sha1Class::result(void) { 100 | // Pad to complete the last block 101 | pad(); 102 | 103 | // Swap byte order back 104 | for (int i=0; i<5; i++) { 105 | uint32_t a,b; 106 | a=state.w[i]; 107 | b=a<<24; 108 | b|=(a<<8) & 0x00ff0000; 109 | b|=(a>>8) & 0x0000ff00; 110 | b|=a>>24; 111 | state.w[i]=b; 112 | } 113 | 114 | // Return pointer to hash (20 characters) 115 | return state.b; 116 | } 117 | 118 | #define HMAC_IPAD 0x36 119 | #define HMAC_OPAD 0x5c 120 | 121 | void Sha1Class::initHmac(const uint8_t* key, int keyLength) { 122 | uint8_t i; 123 | memset(keyBuffer,0,BLOCK_LENGTH); 124 | if (keyLength > BLOCK_LENGTH) { 125 | // Hash long keys 126 | init(); 127 | for (;keyLength--;) write(*key++); 128 | memcpy(keyBuffer,result(),HASH_LENGTH); 129 | } else { 130 | // Block length keys are used as is 131 | memcpy(keyBuffer,key,keyLength); 132 | } 133 | // Start inner hash 134 | init(); 135 | for (i=0; i 5 | #include "Print.h" 6 | 7 | #if ARDUINO < 100 8 | #define __WRITE_RESULT void 9 | #define __WRITE_RETURN(x) return; 10 | #else 11 | #define __WRITE_RESULT size_t 12 | #define __WRITE_RETURN(x) return x; 13 | #endif 14 | 15 | #define HASH_LENGTH 20 16 | #define BLOCK_LENGTH 64 17 | 18 | union _buffer { 19 | uint8_t b[BLOCK_LENGTH]; 20 | uint32_t w[BLOCK_LENGTH/4]; 21 | }; 22 | union _state { 23 | uint8_t b[HASH_LENGTH]; 24 | uint32_t w[HASH_LENGTH/4]; 25 | }; 26 | 27 | class Sha1Class : public Print 28 | { 29 | public: 30 | void init(void); 31 | void initHmac(const uint8_t* secret, int secretLength); 32 | uint8_t* result(void); 33 | uint8_t* resultHmac(void); 34 | virtual __WRITE_RESULT write(uint8_t); 35 | using Print::write; 36 | private: 37 | void pad(); 38 | void addUncounted(uint8_t data); 39 | void hashBlock(); 40 | uint32_t rol32(uint32_t number, uint8_t bits); 41 | _buffer buffer; 42 | uint8_t bufferOffset; 43 | _state state; 44 | uint32_t byteCount; 45 | uint8_t keyBuffer[BLOCK_LENGTH]; 46 | uint8_t innerHash[HASH_LENGTH]; 47 | 48 | }; 49 | extern Sha1Class Sha1; 50 | 51 | #endif 52 | --------------------------------------------------------------------------------