首页 > 代码库 > 内容hash,签名 (Windows Crypt API)

内容hash,签名 (Windows Crypt API)

Windows 提供了Crypto API, 使用这些API, 我们可以比较轻松的实现Hash,签名等工作。MSDN上有很多信息,

http://technet.microsoft.com/zh-cn/library/aa382371

下面的例子是对一个给定的字符串进行hash计算,并且把hash值签名。给定的字符串如下:

BYTE *pbBuffer = (BYTE *)"The data that is to be hashed and signed.";

CryptAcquireContext

第一个需要使用的API 是CryptAcquireContext。

原型如下:

BOOL WINAPI CryptAcquireContext(
  _Out_  HCRYPTPROV *phProv,
  _In_   LPCTSTR pszContainer,
  _In_   LPCTSTR pszProvider,
  _In_   DWORD dwProvType,
  _In_   DWORD dwFlags
);

pszContainter是一个字符串,可以指定key container的名字。

pszProvider指定CSP,如果传NULL的话,那就是default CSP.

dwProvType指定类型

更多信息查看MSDN。

CryptAcquireContext会首先查找指定的CSP,然后再查找指定的key container。如果成功的话,就返回一个CSP的handle(第一个参数)。

下面代码是一个简单的例子。当程序第一次执行的时候,第一个CryptAcquireContext会失败,因为"MyContainer" 密钥容器根本不存在,这样我们就可以创建一个。一旦创建好了,以后就可以直接获取了。

//-------------------------------------------------------------------
    // Acquire a cryptographic provider context handle.

    if (CryptAcquireContext(
        &hProv,
        L"MyContainer",
        NULL,
        PROV_RSA_FULL,
        0))
    {
        printf("CSP context acquired.\n");
    }
    else
    {
        if (GetLastError() == NTE_BAD_KEYSET)
        {
            if (!CryptAcquireContext(
                &hProv,
                L"MyContainer",
                NULL,
                PROV_RSA_FULL,
                CRYPT_NEWKEYSET))
            {
                MyHandleError("Error during CryptAcquireContext.");
            }

        }
        else
        {
            MyHandleError("Error during CryptAcquireContext.");
        }
    }


CryptGetUserKey

这个API可以获取密钥容器里面的密钥,同样第一次调用会失败,因为还不存在,我们可以尝试创建一个。根据MSDN上的说法,一个key container只有一个key。新创建的会覆盖旧的。

if (CryptGetUserKey(
        hProv,
        AT_SIGNATURE,
        &hKey))
    {
        printf("The signature key has been acquired. \n");
    }
    else
    {
        if (GetLastError() == NTE_NO_KEY)               // NTE_NO_KEY意味着密钥不存在,下面就生成一个密钥
        {
            _tprintf(TEXT("The signature key does not exist./n"));
            _tprintf(TEXT("Create a signature key pair./n"));
            if (CryptGenKey(                           // CryptGenKey生成一个密钥
                hProv,                           //指定CSP模块的句柄
                AT_SIGNATURE,                     //对于公钥密码系统,生成一个私钥和一个公钥,这个参数指定了这个密钥是公钥,于是生成了一个密码对。如果不是公钥系统,则指定了密码算法,具体看MSDN。
                0,                                  //指定了生成密钥的类型,这个参数的说明挺多的,想获取更为详尽的资料请看MSDN。
                &hKey))
            {
                _tprintf(TEXT("Created a signature key pair./n"));
            }
            else
            {
                MyHandleError("CryptGenKey failed");
            }
        }
        else
        {
            MyHandleError("Error during CryptGetUserKey for signkey.");
        }
    }


CryptExportKey

我们可以通过CryptExportKey把key里面的公钥导出来,然后可以写到一个文件或者通过网络发送给别人。下面的例子就是导出到一个buffer里面。注意这里的第一个参数就是上面的CryptGetUserKey返回的。

    if (CryptExportKey(
        hKey,
        NULL,
        PUBLICKEYBLOB,
        0,
        NULL,
        &dwBlobLen))
    {
        printf("Size of the BLOB for the public key determined. \n");
    }
    else
    {
        MyHandleError("Error computing BLOB length.");
    }
    //-------------------------------------------------------------------
    // Allocate memory for the pbKeyBlob.

    if (pbKeyBlob = (BYTE*)malloc(dwBlobLen))
    {
        printf("Memory has been allocated for the BLOB. \n");
    }
    else
    {
        MyHandleError("Out of memory. \n");
    }
    //-------------------------------------------------------------------
    // Do the actual exporting into the key BLOB.

    if (CryptExportKey(
        hKey,
        NULL,
        PUBLICKEYBLOB,
        0,
        pbKeyBlob,
        &dwBlobLen))
    {
        printf("Contents have been written to the BLOB. \n");
    }
    else
    {
        MyHandleError("Error during CryptExportKey.");
    }

接下来就给指定的字符串计算一个hash值。

CryptCreateHash 和CryptHashData

先使用CryptCreateHash创建一个hash对象,使用MD5.

然后用这个hash对象把一个指定的buffer计算一个MD5值。最终的hash值保存在hash对象里面hHash。

    //-------------------------------------------------------------------
    // Create the hash object.

    if (CryptCreateHash(
        hProv,
        CALG_MD5,
        0,
        0,
        &hHash))
    {
        printf("Hash object created. \n");
    }
    else
    {
        MyHandleError("Error during CryptCreateHash.");
    }
    //-------------------------------------------------------------------
    // Compute the cryptographic hash of the buffer.

    if (CryptHashData(
        hHash,
        pbBuffer,
        dwBufferLen,
        0))
    {
        printf("The data buffer has been hashed.\n");
    }
    else
    {
        MyHandleError("Error during CryptHashData.");
    }


现在hash值也有了,那么接下来就是签名了。

CryptSignHash

通过这个API可以把hHash里面的hash值(md5)进行签名,也就是使用密钥进行加密。(密钥也存在于hHash中,因为hHash本身也是上面的hProv创建的)

下面的代码先技术签名所需要的大小,然后分配一块内存。这样CryptSignHash成功后,签名后的密文就保存在了pSignature里面。至此,我们成功得到了一段给定内容的hash值的签名(密文)。

    //-------------------------------------------------------------------
    // Determine the size of the signature and allocate memory.

    dwSigLen = 0;
    if (CryptSignHash(
        hHash,
        AT_SIGNATURE,
        NULL,
        0,
        NULL,
        &dwSigLen))
    {
        printf("Signature length %d found.\n", dwSigLen);
    }
    else
    {
        MyHandleError("Error during CryptSignHash.");
    }
    //-------------------------------------------------------------------
    // Allocate memory for the signature buffer.

    if (pbSignature = (BYTE *)malloc(dwSigLen))
    {
        printf("Memory allocated for the signature.\n");
    }
    else
    {
        MyHandleError("Out of memory.");
    }
    //-------------------------------------------------------------------
    // Sign the hash object.

    if (CryptSignHash(
        hHash,
        AT_SIGNATURE,
        NULL,
        0,
        pbSignature,
        &dwSigLen))
    {
        printf("pbSignature is the hash signature.\n");
    }
    else
    {
        MyHandleError("Error during CryptSignHash.");
    }


现在我们有了

1. 内容:BYTE *pbBuffer = (BYTE *)"The data that is to be hashed and signed.";

2. 签名后的hash值(密文)pSignature

3. 公钥 pbKeyblob

我们可以把这3个信息发给对方,然后对方需要

1. 用公钥把签名进行解密(其实还要先验证公钥(证书),这是另外一码事)

2. 把收到的内容进行hash计算

3. 把#1得到的签名明文和#2里面计算出来的hash值进行比较,如果一样的话,就说明数据有效。如果不一样,那么就说明数据被破坏了,有可能传输过程中被修改了,或者丢了。注意,至于别人冒充的问题,其实是通过验证公钥来确保的,一般需要把公钥和用户信息发给对方,也就是证书。这样对方就可以验证证书的真伪。这个是用来防止有人冒充,使用他们自己的证书。比如正常交易双方是A和B,A是客户。C可以截获这个网络包,然后C伪造内容,并且用C自己的私钥签名,并且把含有公钥的证书发给B。如果B不检查发过来的证书,那么就可以被骗。有关证书的鉴别就需要用到CA了。证书本身也是有签名的,证书签名的公钥在当前证书的上一级证书里面,直到最后的根证书(也就是CA拥有的那个根证书)。

OK, 有关具体的验证签名下次再介绍。




内容hash,签名 (Windows Crypt API)