Implemented RFC 4226 - HOTP: An HMAC based One-Time-Password Algorithm for BlackBerry 10 running QNX 6. Uses BlackBerry Cryptographic Kernel version 5.6 (SB-GSE-56). UI is pending and the app in the current state can be found at GitHub called QAuthenticator. I must say, BlackBerry cryptography library is just fantastic.
A reference implementation of the algorithm in Groovy follows.
/**
* [RFC 4226] HOTP Algorithm - Reference Implementation
* @author Jaseem V V
* @date Sunday 03 September 2017
*/
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
byte[] hmac_sha1(byte[] keyBytes, byte[] text) {
Mac hmacSha1 = Mac.getInstance("HmacSHA1");
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
hmacSha1.init(macKey);
return hmacSha1.doFinal(text);
}
doubleDigits = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
int calcChecksum(long num, int digits) {
doubleDigit = true;
total = 0;
while (0 < digits--) {
digit = (int) (num % 10);
num /= 10;
if (doubleDigit) {
digit = doubleDigits[digit];
}
total += digit;
doubleDigit = !doubleDigit;
}
result = total % 10;
if (result > 0) {
result = 10 - result;
}
return result;
}
def getMovingFactor(long counter) {
byte[] text = new byte[8];
for (int i = text.length - 1; i >= 0; i--) {
text[i] = (byte) (counter & 0xff);
counter >>= 8;
}
text
}
def baToHex(ba) {
String digest = ""
ba.each {
digest += String.format("%02x", it)
}
digest
}
def truncate(digest_ba, truncationOffset) {
offset = digest_ba[digest_ba.size() - 1] & 0xf
if ((0<=truncationOffset) && (truncationOffset < (digest_ba.length-4))) {
offset = truncationOffset;
}
(digest_ba[offset] & 0x7f) << 24 | (digest_ba[offset+1] & 0xff) << 16 | (digest_ba[offset+2] & 0xff) << 8 | (digest_ba[offset+3] & 0xff)
}
def getOtp(key, counter, codeDigits, addChecksum, truncationOffset) {
// 0 1 2 3 4 5 6 7 8
DIGITS_POWER = [1,10,100,1000,10000,100000,1000000,10000000,100000000];
digest_ba = hmac_sha1(key.getBytes(), getMovingFactor(counter));
println "Digest: ${baToHex(digest_ba)}"
snum = truncate(digest_ba, truncationOffset)
println "Decimal ${snum}"
otp = snum % DIGITS_POWER[codeDigits]
(addChecksum) ? (otp * 10) + calcChecksum(otp, codeDigits) : otp
}
key = "12345678901234567890"
counter = 1
codeDigits = 6
truncationOffset = -1
println "HOTP: ${getOtp(key, counter, codeDigits, false, truncationOffset)}"