一直觉得工行密码器很神奇,网上看到很多人在分析原理,讨论它有没有和服务端通信等等。最近智能手机上的宝令越来越多,更觉得好玩。找了一段时间,找到一个代码,顺便找到了他的算法标准。原来这种算法2005年已经出来了。其实是基于SHA摘要算法弄出来的一大串序列,因为摘要算法不可逆,你知道一个6位数口令,并不能计算出密钥而得到下一个口令。
我在想把它用在一些软件接口中做认证是否有意义,用来替代复杂的PKI,提升性能,而且对人友好。
标准:RFC4226 HOTP An HMAC-Based One-Time Password Algorithm 可以再搜索引擎搜到英文版
Java实现:
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class MessageAuthenticationExample {
// These are used to calculate the check-sum digits.
// 0 1 2 3 4 5 6 7 8 9
private static final int[] doubleDigits =
{ 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
/**
* Calculates the checksum using the credit card algorithm.
* This algorithm has the advantage that it detects any single
* mistyped digit and any single transposition of
* adjacent digits.
*
* @param num the number to calculate the checksum for
* @param digits number of significant places in the number
*
* @return the checksum of num
*/
public static int calcChecksum(long num, int digits) {
boolean doubleDigit = true;
int total = 0;
while (0 < digits--) {
int digit = (int) (num % 10);
num /= 10;
if (doubleDigit) {
digit = doubleDigits[digit];
}
total += digit;
doubleDigit = !doubleDigit;
}
int result = total % 10;
if (result > 0) {
result = 10 - result;
}
return result;
}
/**
* This method uses the JCE to provide the HMAC-SHA-1
* algorithm.
* HMAC computes a Hashed Message Authentication Code and
* in this case SHA1 is the hash algorithm used.
*
* @param keyBytes the bytes to use for the HMAC-SHA-1 key
* @param text the message or text to be authenticated.
*
* @throws NoSuchAlgorithmException if no provider makes
* either HmacSHA1 or HMAC-SHA-1
* digest algorithms available.
* @throws InvalidKeyException
* The secret provided was not a valid HMAC-SHA-1 key.
*
*/
public static byte[] hmac_sha1(byte[] keyBytes, byte[] text)
throws NoSuchAlgorithmException, InvalidKeyException
{
// try {
Mac hmacSha1;
try {
hmacSha1 = Mac.getInstance("HmacSHA1");
} catch (NoSuchAlgorithmException nsae) {
hmacSha1 = Mac.getInstance("HMAC-SHA-1");
}
SecretKeySpec macKey =
new SecretKeySpec(keyBytes, "RAW");
hmacSha1.init(macKey);
return hmacSha1.doFinal(text);
// } catch (GeneralSecurityException gse) {
// throw new UndeclaredThrowableException(gse);
// }
}
private static final int[] DIGITS_POWER
// 0 1 2 3 4 5 6 7 8
= {1,10,100,1000,10000,100000,1000000,10000000,100000000};
/**
* This method generates an OTP value for the given
* set of parameters.
*
* @param secret the shared secret
* @param movingFactor the counter, time, or other value that
* changes on a per use basis.
* @param codeDigits the number of digits in the OTP, not
* including the checksum, if any.
* @param addChecksum a flag that indicates if a checksum digit
* should be appended to the OTP.
* @param truncationOffset the offset into the MAC result to
* begin truncation. If this value is out of
* the range of 0 ... 15, then dynamic
* truncation will be used.
* Dynamic truncation is when the last 4
* bits of the last byte of the MAC are
* used to determine the start offset.
* @throws NoSuchAlgorithmException if no provider makes
* either HmacSHA1 or HMAC-SHA-1
* digest algorithms available.
* @throws InvalidKeyException
* The secret provided was not
* a valid HMAC-SHA-1 key.
*
* @return A numeric String in base 10 that includes
* {@link codeDigits} digits plus the optional checksum
* digit if requested.
*/
static public String generateOTP(byte[] secret,
long movingFactor,
int codeDigits,
boolean addChecksum,
int truncationOffset)
throws NoSuchAlgorithmException, InvalidKeyException
{
// put movingFactor value into text byte array
String result = null;
int digits = addChecksum ? (codeDigits + 1) : codeDigits;
byte[] text = new byte[8];
for (int i = text.length - 1; i >= 0; i--) {
text[i] = (byte) (movingFactor & 0xff);
movingFactor >>= 8;
}
// compute hmac hash
byte[] hash = hmac_sha1(secret, text);
// put selected bytes into result int
int offset = hash[hash.length - 1] & 0xf;
if ( (0<=truncationOffset) &&
(truncationOffset<(hash.length-4)) ) {
offset = truncationOffset;
}
int binary =
((hash[offset] & 0x7f) << 24)
| ((hash[offset + 1] & 0xff) << 16)
| ((hash[offset + 2] & 0xff) << 8)
| (hash[offset + 3] & 0xff);
int otp = binary % DIGITS_POWER[codeDigits];
if (addChecksum) {
otp = (otp * 10) + calcChecksum(otp, codeDigits);
}
result = Integer.toString(otp);
while (result.length() < digits) {
result = "0" + result;
}
return result;
}
public static byte[] hexStringToByte(String hex) {
int len = (hex.length() / 2);
byte[] result = new byte[len];
char[] achar = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
}
return result;
}
private static byte toByte(char c) {
byte b = (byte) "0123456789ABCDEF".indexOf(c);
return b;
}
/**
* @param args
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public static void main(String[] args) throws
UnsupportedEncodingException,
NoSuchAlgorithmException, InvalidKeyException {
if (args == null || args.length == 0) {
System.out.println("Usage:");
System.out.println("java testOTP share_secret count");
}
// byte[] plainText = args[0].getBytes("UTF-8");
byte[] plainText = hexStringToByte("8F9811E09306A1EACEDFE2AC2545940A2512E328");
/*for (int j = 0; j < 40; j++) {
System.out.println(Integer.toHexString(plainText[j]));
//System.out.println("count: " + i + ", otp: " + HOTPAlgorithm.generateOTP(text, i, 6,false,16));
}*/
// String temp1 = "12345678901234567890";
// byte[] plainText = temp1.getBytes("UTF-8");
//long count = 1014;//Long.parseLong(args[1].trim());
boolean addChecksum = false;
//String mactext = generateOTP(plainText, count, 6, addChecksum,16);
for (int i = 1; i < 1000; i++) {
System.out.println("count: " + i + ", otp: " + generateOTP(plainText, i, 6, addChecksum,16));
//System.out.println("count: " + i + ", otp: " + HOTPAlgorithm.generateOTP(text, i, 6,false,16));
}
// System.out.println("Share Secret: "+args[0]);
// System.out.println("Count: "+ count);
// System.out.println("OTP: "+ mactext);
// System.out.println("Original Text:"+args[0]);
// System.out.println("Original Text:"+plainText);
// System.out.println("\n" + mac.getProvider().getInfo());
// System.out.println("\nMAC: ");
// System.out.println(new String(mac.doFinal(), "UTF-8"));
}
}
C++实现:
// HmacOtp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#define cbSHA1_RESULT 20
#define cbMD5_RESULT 16
#define cbHMAC_PADDING 64
#define cbMIN_OTP 6
DWORD g_rgdwDigitsPower [] =
// 0 1 2 3 4 5 6 7 8
{1,10,100,1000,10000,100000,1000000,10000000,100000000};
//
// Heap helpers
//
LPVOID Alloc(DWORD cb)
{
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cb);
}
void Free(LPVOID pv)
{
HeapFree(GetProcessHeap(), 0, pv);
}
//
// Flow macros
//
static
void
WINAPI
_OutputDbgStr(
__in LPSTR szMsg,
__in DWORD dwStatus)
{
CHAR rgsz [256];
StringCbPrintfA(
rgsz,
sizeof(rgsz),
"ERROR: %s - 0x%x\n",
szMsg,
dwStatus);
OutputDebugStringA(rgsz);
}
#define TRY_DWORD(_X) { \
if (ERROR_SUCCESS != (status = _X)) { \
_OutputDbgStr(#_X, status); \
__leave; \
} \
}
#define TRY_BOOL(_X) { \
if (FALSE == (_X)) { \
status = GetLastError(); \
_OutputDbgStr(#_X, status); \
__leave; \
} \
}
#define TRY_ALLOC(_X) { \
if (NULL == (_X)) { \
status = ERROR_NOT_ENOUGH_MEMORY; \
__leave; \
} \
}
//
// Generate an RFC 2104 HMAC value
//
DWORD
WINAPI
GenerateHMAC(
__in ALG_ID aiHash,
__in_ecount(cbSecret) PBYTE pbSecret,
__in DWORD cbSecret,
__in_ecount(cbMsg) PBYTE pbMsg,
__in DWORD cbMsg,
__out_ecount(cbHMAC) PBYTE pbHMAC,
__in DWORD cbHMAC)
{
DWORD status = ERROR_SUCCESS;
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
BYTE rgbIPad [cbHMAC_PADDING];
BYTE rgbOPad [cbHMAC_PADDING];
DWORD dwIndex = 0;
BYTE bTemp = 0;
__try
{
//
// Check input
//
if (cbSecret > cbHMAC_PADDING)
{
status = ERROR_INVALID_PARAMETER;
__leave;
}
//
// Set the padding strings
//
ZeroMemory(rgbIPad, sizeof(rgbIPad));
ZeroMemory(rgbOPad, sizeof(rgbOPad));
memcpy(rgbIPad, pbSecret, cbSecret);
memcpy(rgbOPad, pbSecret, cbSecret);
for ( ; dwIndex < cbHMAC_PADDING; dwIndex++)
{
rgbIPad [dwIndex] ^= 0x36;
rgbOPad [dwIndex] ^= 0x5C;
}
// Reverse both padding strings in place
for (dwIndex = 0; dwIndex < cbHMAC_PADDING; dwIndex++)
{
bTemp = rgbIPad [dwIndex];
rgbIPad [dwIndex] = rgbIPad [cbHMAC_PADDING - dwIndex - 1];
rgbIPad [cbHMAC_PADDING - dwIndex - 1] = bTemp;
bTemp = rgbOPad [dwIndex];
rgbOPad [dwIndex] = rgbOPad [cbHMAC_PADDING - dwIndex - 1];
rgbOPad [cbHMAC_PADDING - dwIndex - 1] = bTemp;
}
//
// Get the inner hash
//
TRY_BOOL(CryptAcquireContext(
&hProv,
NULL,
MS_ENHANCED_PROV,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT));
TRY_BOOL(CryptCreateHash(hProv, aiHash, 0, 0, &hHash));
TRY_BOOL(CryptHashData(hHash, rgbIPad, sizeof(rgbIPad), 0));
TRY_BOOL(CryptHashData(hHash, pbMsg, cbMsg, 0));
TRY_BOOL(CryptGetHashParam(hHash, HP_HASHVAL, pbHMAC, &cbHMAC, 0));
CryptDestroyHash(hHash);
hHash = 0;
//
// Get the outer hash
//
TRY_BOOL(CryptCreateHash(hProv, aiHash, 0, 0, &hHash));
TRY_BOOL(CryptHashData(hHash, rgbOPad, sizeof(rgbOPad), 0));
TRY_BOOL(CryptHashData(hHash, pbHMAC, cbHMAC, 0));
TRY_BOOL(CryptGetHashParam(hHash, HP_HASHVAL, pbHMAC, &cbHMAC, 0));
}
__finally
{
if (0 != hHash)
CryptDestroyHash(hHash);
if (0 != hProv)
CryptReleaseContext(hProv, 0);
}
return status;
}
//
// Generate an OTP value per RFC 4226.
//
extern "C" __declspec(dllexport)
DWORD
WINAPI
GenerateOTP(
__in_ecount(cbSecret) PBYTE pbSecret,
__in DWORD cbSecret,
__in DWORD64 qwCount,
__out_ecount(cchOTP) LPSTR szOTP,
__in DWORD cchOTP)
{
DWORD status = ERROR_SUCCESS;
BYTE rgbText [sizeof(qwCount)];
DWORD iText = 0;
BYTE rgbHash [cbSHA1_RESULT];
DWORD dwOffset = 0;
DWORD dwBinary = 0;
DWORD dwOTP = 0;
__try
{
//
// Check input
//
if (cchOTP > (sizeof(g_rgdwDigitsPower) + 1) ||
cchOTP < cbMIN_OTP)
{
status = ERROR_INVALID_PARAMETER;
__leave;
}
//
// Get the text
//
for ( ; iText < sizeof(rgbText); iText++)
{
rgbText [sizeof(rgbText) - iText - 1] = (BYTE) qwCount & 0xFF;
qwCount >>= 8;
}
//
// Get the HMAC result
//
TRY_DWORD(GenerateHMAC(
CALG_SHA1,
pbSecret,
cbSecret,
rgbText,
sizeof(rgbText),
rgbHash,
sizeof(rgbHash)));
//
// Compute the OTP from the HMAC result
//
dwOffset = rgbHash [cbSHA1_RESULT - 1] & 0xF;
dwBinary = ((rgbHash [dwOffset] & 0x7F) << 24) |
((rgbHash [dwOffset + 1] & 0xFF) << 16) |
((rgbHash [dwOffset + 2] & 0xFF) << 8) |
(rgbHash [dwOffset + 3] & 0xFF);
dwOTP = dwBinary % g_rgdwDigitsPower [cchOTP - 1];
_itoa_s(dwOTP, szOTP, cchOTP, 10);
}
__finally
{
}
return status;
}