Skip to content

Commit 5dad877

Browse files
committed
pad with zeros
1 parent 770038d commit 5dad877

File tree

3 files changed

+120
-121
lines changed

3 files changed

+120
-121
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ gauthenticator.so
77
gauthenticator.dylib
88
gauthenticator.dll
99
gauthenticator.a
10+
libgauthenticator.a
1011
gauthenticator.lib
1112
gauthenticator-test-*
1213
*.exe

readme.md

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
## Google Authenticator
2-
A small D program to generate the google authenticator code.
2+
A small D library to generate the google authenticator code.
33

44
inspired from https://github.com/tilaklodha/google-authenticator
55

@@ -8,10 +8,19 @@ inspired from https://github.com/tilaklodha/google-authenticator
88
- On OSX run `brew install dub`.
99
- Follow instructions on https://code.dlang.org/ for other OSes.
1010

11-
- Provide your 16-digit secret token in secrets.pem file
1211

13-
The auth code works on the secret token and the current time. The time on your local machine should be in sync according to NTP.
12+
The OTP auth code works on the secret token and the current time.
13+
The time on your local machine should be in sync according to NTP.
14+
The secret token usually is given to the user on the first configuration as a base32-encoded string or acquired via QR code.
15+
16+
The library exposes two functions:
17+
18+
- getHOTPToken
19+
given a "secret" and a time interval, returns the 6-digit HOTP Token as a string
20+
- getTOTPToken
21+
given a "secret", returns the 6-digit TOTP Token as a string using the current time
22+
23+
24+
1425

15-
- Run `sudo ntpdate time.nist.gov` to sync time
16-
- Run `rdmd example\runme.d`.
1726

source/gauthenticator.d

+105-116
Original file line numberDiff line numberDiff line change
@@ -20,135 +20,124 @@ SOFTWARE.
2020

2121
module gauthenticator;
2222

23-
import std.stdio;
24-
import std.digest.sha;
23+
import std.digest.hmac : HMAC;
24+
import std.digest.sha : SHA1,toHexString;
25+
import std.bitmanip : nativeToBigEndian,bigEndianToNative;
26+
import std.format : format;
2527

26-
class GAuthenticator
27-
{
28-
29-
// HMAC-based One Time Password(HOTP)
30-
public string getHOTPToken(const string secret, const ulong interval)
31-
{
32-
33-
auto key=base32decode(secret);
34-
SHA1 hash;
35-
hash.start();
36-
hash.put(key);
37-
ubyte[20] sha1sum = hash.finish();
38-
int offset=(sha1sum[19] & 15);
39-
40-
41-
/*
42-
hash = HMAC-SHA1(key)
43-
offset = last nibble of hash
44-
truncatedHash := hash[offset..offset+3] //4 bytes starting at the offset
45-
Set the first bit of truncatedHash to zero //remove the most significant bit
46-
code := truncatedHash mod 1000000
47-
pad code with 0 from the left until length of code is 6
48-
return code */
49-
50-
return "000000";
51-
}
28+
// code adapted from https://github.com/tilaklodha/google-authenticator
5229

53-
//Time-based One Time Password(TOTP)
54-
public string getTOTPToken(const string secret)
55-
{
56-
//The TOTP token is just a HOTP token seeded with every 30 seconds.
57-
import std.datetime : Clock;
30+
// HMAC-based One Time Password(HOTP)
31+
public string getHOTPToken(const string secret, const ulong interval)
32+
{
33+
//secret is a base32 encoded string. Converts to a byte array
34+
auto key = base32decode(secret);
35+
//Signing the value using HMAC-SHA1 Algorithm
36+
auto hm = HMAC!SHA1(key);
37+
hm.put(nativeToBigEndian(interval));
38+
ubyte[20] sha1sum = hm.finish();
39+
// We're going to use a subset of the generated hash.
40+
// Using the last nibble (half-byte) to choose the index to start from.
41+
// This number is always appropriate as it's maximum decimal 15, the hash will
42+
// have the maximum index 19 (20 bytes of SHA1) and we need 4 bytes.
43+
const int offset = (sha1sum[19] & 15);
44+
ubyte[4] h = sha1sum[offset .. offset + 4];
45+
//Ignore most significant bits as per RFC 4226.
46+
//Takes division from one million to generate a remainder less than < 7 digits
47+
const uint h12 = (bigEndianToNative!uint(h) & 0x7fffffff) % 1_000_000;
48+
return format("%06d",h12);
49+
}
5850

59-
immutable ulong interval = Clock.currTime().toUnixTime() / 30;
60-
return getHOTPToken(secret, interval);
61-
}
51+
//Time-based One Time Password(TOTP)
52+
public string getTOTPToken(const string secret)
53+
{
54+
//The TOTP token is just a HOTP token seeded with every 30 seconds.
55+
import std.datetime : Clock;
56+
immutable ulong interval = Clock.currTime().toUnixTime() / 30;
57+
return getHOTPToken(secret, interval);
58+
}
6259

63-
//RFC 4648
64-
package ubyte[] base32decode(const string message)
60+
//RFC 4648
61+
private ubyte[] base32decode(const string message)
62+
{
63+
int buffer = 0;
64+
int bitsLeft = 0;
65+
ubyte[] result;
66+
for (int i = 0; i < message.length; i++)
6567
{
66-
int buffer = 0;
67-
int bitsLeft = 0;
68-
ubyte[] result;
69-
for (int i = 0; i < message.length; i++)
68+
int ch = message[i];
69+
if (ch == '=')
70+
break;
71+
if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-')
7072
{
71-
int ch = message[i];
72-
if (ch == '=')
73-
break;
74-
if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-')
75-
{
76-
continue;
77-
}
78-
buffer = buffer << 5;
79-
80-
// Deal with commonly mistyped characters
81-
if (ch == '0')
82-
{
83-
ch = 'O';
84-
}
85-
else if (ch == '1')
86-
{
87-
ch = 'L';
88-
}
89-
else if (ch == '8')
90-
{
91-
ch = 'B';
92-
}
93-
94-
// Look up one base32 digit
95-
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
96-
{
97-
ch = (ch & 0x1F) - 1;
98-
}
99-
else if (ch >= '2' && ch <= '7')
100-
{
101-
ch -= ('2' - 26);
102-
}
103-
104-
buffer |= ch;
105-
bitsLeft += 5;
106-
if (bitsLeft >= 8)
107-
{
108-
int c = (buffer >> (bitsLeft - 8));
109-
result ~= cast(byte)(c & 0xff);
110-
bitsLeft -= 8;
111-
}
73+
continue;
74+
}
75+
buffer = buffer << 5;
11276

77+
// Deal with commonly mistyped characters
78+
if (ch == '0')
79+
{
80+
ch = 'O';
81+
}
82+
else if (ch == '1')
83+
{
84+
ch = 'L';
85+
}
86+
else if (ch == '8')
87+
{
88+
ch = 'B';
11389
}
114-
return result;
11590

116-
}
117-
//test base32 decoder
118-
unittest
119-
{
120-
auto ga = new GAuthenticator;
121-
byte[] expected = ['F', 'O', 'O', 'B', 'A', 'R'];
122-
auto message = ga.base32decode("IZHU6QSBKI");
123-
assert(message == expected);
124-
expected = ['1', '2', '3', '4', '5', 't', 'e', 's', 't'];
125-
message = ga.base32decode("GEZDGNBVORSXG5A=");
126-
assert(message == expected);
127-
}
91+
// Look up one base32 digit
92+
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
93+
{
94+
ch = (ch & 0x1F) - 1;
95+
}
96+
else if (ch >= '2' && ch <= '7')
97+
{
98+
ch -= ('2' - 26);
99+
}
128100

101+
buffer |= ch;
102+
bitsLeft += 5;
103+
if (bitsLeft >= 8)
104+
{
105+
const c = (buffer >> (bitsLeft - 8));
106+
result ~= cast(byte)(c & 0xff);
107+
bitsLeft -= 8;
108+
}
129109

130-
// SHA1 test
131-
unittest
132-
{
133-
SHA1 hash;
134-
hash.start();
135-
ubyte[] data = ['a', 'b', 'c'];
136-
hash.put(data);
137-
ubyte[20] result = hash.finish();
138-
//writeln(toHexString(result));
139-
assert(toHexString(result)=="A9993E364706816ABA3E25717850C26C9CD0D89D");
140110
}
111+
return result;
141112

142-
/*
143-
unittest
144-
{
145-
auto ga = new GAuthenticator;
146-
auto secret = "dummySECRETdummy";
147-
auto interval = ulong(50780342);
148-
auto otp = "971294";
149-
assert(otp == ga.getHOTPToken(secret, interval));
150-
}
151-
*/
113+
}
114+
//test for base32 decoder
115+
unittest
116+
{
117+
auto expected = cast(byte[])("FOOBAR");
118+
auto message = base32decode("IZHU6QSBKI");
119+
assert(message == expected);
120+
expected = cast(byte[])("12345test");
121+
message = base32decode("GEZDGNBVORSXG5A=");
122+
assert(message == expected);
123+
}
152124

125+
// test for SHA1 func
126+
unittest
127+
{
128+
SHA1 hash;
129+
hash.start();
130+
auto data = cast(ubyte[])"abc";
131+
hash.put(data);
132+
ubyte[20] result = hash.finish();
133+
assert(toHexString(result) == "A9993E364706816ABA3E25717850C26C9CD0D89D");
153134
}
154135

136+
//final test for OTP functionality (with a fixed time)
137+
unittest
138+
{
139+
auto secret = "dummySECRETdummy";
140+
auto interval = ulong(50_780_342); // D allows underscore in numbers to improve readability
141+
const otp = "971294";
142+
assert(otp == getHOTPToken(secret, interval));
143+
}

0 commit comments

Comments
 (0)