寻找支付宝令、工行密码器等的算法基本原理

一直觉得工行密码器很神奇,网上看到很多人在分析原理,讨论它有没有和服务端通信等等。最近智能手机上的宝令越来越多,更觉得好玩。找了一段时间,找到一个代码,顺便找到了他的算法标准。原来这种算法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;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值