提醒:本页面将不再更新、维护或者支持,文章、评论所叙述内容存在时效性,涉及技术细节或者软件使用方面不保证能够完全有效可操作,请谨慎参考!

原文发表于2008年9月13日 标题是《C/C++使用本地CryptoAPI实现随机字符串》

随机字符串的应用还是比较广泛的,比如随机的窗体标题(防止被FindWindow),随机的加密密匙,用户注册的随机密码等等。

实现一个随机字符串,很多朋友可能会想到标准的C/C++头文件提供的库函数,但是这通常是需要引入C/C++运行时支持库的,有时在做一个纯Win32 API的程序肯定要考虑程序代码的通用以及较小的体积的问题,下面我用代码说明一下如何使用本地的CryptoAPI实现随机字符串。

如果有朋友对于CryptoAPI不太了解的话可以去查看MSDN的相关说明,这套API为安全方面的加密解密等等提供了良好的支持。

由于CryptoAPI生成的不是随机的ASCII字符串,所以我们需要一个函数,其作用是将16进制转换为ASCII码。(这段代码如需在C语言环境下工作请将函数参数引用以指针方式实现)

//
//  函数功能: 将一个字节的16进制的高位和低位变为ASCII码输出
//  参数: HighPart  --  输出的高位的ASCII码
//    LowPart  --  输出的低位ASCII码
//    lpbSrc  --  传入的字节数据
//  返回: VOID
//
void HexToChar(
  TCHAR &HighPart,
  TCHAR &LowPart,
  const LPBYTE &lpbSrc
)
{
  BYTE bytHPart = *(lpbSrc) >> 4;  //  右移4位,将高位移到低位
  BYTE bytLPart = *(lpbSrc) &  0xf; //  屏蔽高位,保留低位
  
  __asm
  {
  mov al, bytLPart
  mov ah, bytHPart
  cmp al, 0ah
  jb  ASM_TAG1
  add al, 07h
ASM_TAG1:
  add al, 30h
  mov bytLPart, al

  cmp ah, 0ah
  jb  ASM_TAG2
  add ah, 07h
ASM_TAG2:
  add ah, 30h
  mov bytHPart, ah
  }

  HighPart = (TCHAR)bytHPart;
  LowPart = (TCHAR)bytLPart;
}

下面是主要函数。

//
//  函数功能: 产生随机的字符串
//  参数: Buf    --  输出字符串的数组地址
//        MaxLength  --  随机字符串最大长度
//
BOOL GetRandString(LPTSTR Buf, DWORD MaxLength)
{
  HCRYPTPROV hCryptProv=0;
  BOOL   retVal;
  DWORD  i, j=0;
  TCHAR  chFri,chSec;
  LPBYTE pbCode  =  NULL;
  HANDLE hHeap  =  NULL;

  retVal=CryptAcquireContext(
      &hCryptProv,
      NULL,
      NULL,
      PROV_RSA_FULL,
      0);
  if(!retVal) {
    goto exit;
  }

  hHeap = GetProcessHeap();  //  获得当前进程的堆
  if(NULL == hHeap) {
    goto exit;
  }

  if(NULL == (pbCode = (LPBYTE)HeapAlloc(
    hHeap,HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY , 
    sizeof(BYTE)*MaxLength)))  //  使用HeapAlloc在堆上分配空间
  {
    goto exit;
  }

  retVal=CryptGenRandom(
    hCryptProv,
    MaxLength,
    pbCode);  //  产生随机值
  if(!retVal)  {
    goto exit;
  }
  
  //  依次读取随机值并生成ASCII码
  for(i = 0 ; i < MaxLength ; i++) {
    HexToChar(chFri, chSec, pbCode+i);
    *(Buf + j)=chFri;
    *(Buf + j + 1)=chSec;
    j+=2;
  }

  *(Buf + j)='\0';
exit:
  if(hCryptProv) 
   CryptReleaseContext(hCryptProv,0);
  if(NULL != hHeap)
    HeapFree(hHeap, HEAP_NO_SERIALIZE, (LPBYTE)pbCode);
  return retVal;
}

2011年3月28日重要更新

偶尔从旧博客上翻到这篇文章,现在回想起来那时真是学汇编学傻了,其实完全没必要用汇编另外写一个艰涩难懂又不可移植的函数,而且这个函数也没有对字符串做很好的处理, 存在缓冲溢出漏洞,是不安全的 ,我们可以简单一点,像下面那样:

// lpszBuffer -- 字符串缓冲区指针
// cchSize    -- 字符串缓冲区能容纳字符的个数
// nMaxLength -- 随机字符串的最大长度
LPTSTR StringFromRandom(
  LPTSTR lpszBuffer, int cchSize, int nMaxLength
)
{
  HCRYPTPROV hCryptProv = 0U;
  LPBYTE  lpbBuffer = NULL;
  int i;
  __try {
    if (!CryptAcquireContext(
      &hCryptProv,
      NULL,
      NULL,
      PROV_RSA_FULL,
      0
      ))
      __leave;

    lpbBuffer = (LPBYTE)malloc(
      sizeof(BYTE) * nMaxLength
      );
    if (NULL == lpbBuffer)
      __leave;
    if (!CryptGenRandom(
      hCryptProv,
      sizeof(BYTE) * nMaxLength,
      lpbBuffer))
      __leave;

    memset(lpszBuffer, 0, sizeof(TCHAR) * cchSize);

    for (i = 0; i<__min(cchSize - 1, nMaxLength); i++) {
      StringCchPrintf(
        lpszBuffer + i,
        cchSize - i,
        TEXT("%X"),
        *(lpbBuffer+i));
    }
  } __finally {
    if (lpbBuffer)
      free(lpbBuffer);
    if(hCryptProv)    
      CryptReleaseContext(hCryptProv,0);
  }

  return lpszBuffer;
}