• IDOS-CryptoAPI、PKCS#11


    CryptoAPI

    CryptoAPI是Win32平台下实现密码运算的一整套接口(当然你在Windows 64也可以用),在Windows下做密码运算基本绕不过它。今天开始,就介绍一下如何调用CryptoAPI实现几个主要的密码运算功能。

    一、摘要

           (1)可以按照如下顺序调用接口实现摘要:
           BOOL CryptAcquireContext (
           HCRYPTPROV * phProv,
           LPCTSTR pszContainer,
           LPCTSTR pszProvider,
           DWORD dwProvType,
           DWORD dwFlags
           )

    •        摘要运算的第一步要调用CryptAcquireContext方法。实际上,下面介绍的每一个密码运算基本都要先通过调用此方法,设置相应的密码运算参数,并返回相应的CSP句柄,用于后面的运算。phProv是返回的CSP句柄;pszContainer是要使用的密钥是在容器;摘要运算不涉及密钥,所以这里设置为NULL;pszProvider为使用到的CSP的名称,如果设置为NULL,则CryptAcquireContext会调用系统缺省CSP;dwProvType为所使用的CSP的类型,一般这里设置为PROV_RSA_FULL(0x1);dwFlags为标志值,如果是涉及到私钥的运算,如签名或解密,应设置为0,但如果是摘要、加密或验证等不涉及私钥的运算,强烈不建议这里设置成0,而应设置成CRYPT_VERIFYCONTEXT(0xF0000000),就是告诉Windows接下来的密码运算是不会访问私钥的。这样做的原因是,在实际应用中,摘要、加密或验证一般采取软实现的方式,尤其是加密和验证,即采用Windows自带的CSP。这种情况下,如果dwFlags设置成0,Windows的CSP会试图访问其私钥存储区域。而Windows CSP保护其私钥存储区域的口令是使用Windows系统管理员账户的口令加密的。因此,如果用户修改过Windows系统管理员账户的口令,那么其保护私钥存储区域的口令将无法解密,就会造成已经存在的私钥存储区域访问失败,传导到上层的CryptAcquireContext方法失败。因此,为了减少上述不必要的麻烦,切记这里的dwFlags参数如无必须,应设置成CRYPT_VERIFYCONTEXT。
             此方法调用成功返回true(-1),否则返回false(0),并可以调用GetLastError返回具体错误信息。

           BOOL CryptCreateHash(
           HCRYPTPROV hProv,
           ALG_ID Algid,
           HCRYPTKEY hKey,
           DWORD dwFlags,
           HCRYPTHASH * phHash
           )
           调用此方法生成一个摘要运算的对象。hProv为上一步返回的CSP句柄;Algid为摘要算法,比如可以是CALG_SHA1;hKey和dwFlags都设置成0;phHash为返回的摘要运算对象。返回值同上。

           BOOL CryptHashData(
           HCRYPTHASH hHash,
           BYTE * pbData,
           DWORD dwDataLen,
           DWORD dwFlags
           )
           调用CryptHashData方法进行摘要运算。phHash为上一步返回的摘要运算对象;pbData为原文;dwDataLen为原文长度;dwFlags为0。方法返回值同上。

           BOOL CryptGetHashParam(
           HCRYPTHASH hHash,
           DWORD dwParam,
           BYTE *pbData,
           DWORD *pdwDataLen,
           DWORD dwFlags
           )
           调用CryptGetHashParam可以返回摘要的各种相关数据信息,这里先返回摘要的数据长度。dwParam设置为HP_HASHSIZE(0x0004);pbData为返回长度值;pdwDataLen为长度值所占字节数;dwFlags为0。调用成功后,再调用一次CryptGetHashParam方法返回摘要值。这次dwParam设置为HP_HASHVAL(0x0002);按照上一次调用返回的长度值为pbData分配空间,它返回的摘要值。

    二、 对称加密

    对称加密中常用的方式是根据用户输入的口令加解密文档,即基于口令派生出加解密密钥,调用步骤如下。

    CryptAcquireContext
    返回CSP句柄,参数设置与摘要运算时一致。
    CryptCreateHash
    生成摘要运算对象。
    CryptHashData
    生成摘要。pbData为调用加密功能的上位程序输入的加密口令。

    BOOL CryptDeriveKey(
    HCRYPTPROV hProv,
    ALG_ID Algid,
    HCRYPTHASH hBaseData,
    DWORD dwFlags,
    HCRYPTKEY * phKey
    )

    • 派生密钥。Algid为加密算法,比如CALG_DES、CALG_3DES什么的;hBaseData就是上一步返回的摘要对象;dwFlags是密钥类型,如果调用的CSP没有特别要求,设置为0;phKey为返回的密钥对象。
      也可以调用CryptGenKey生成一个会话密钥,用来加密数据,而这个会话密钥可以使用数据接收者的公钥加密传输。不过这种方式实际已经包含在非对称加解密中,因此很少直接拿来用。

    BOOL CryptSetKeyParam(
    HCRYPTKEY hKey,
    DWORD dwParam,
    BYTE * pbData,
    DWORD dwFlags
    )

    • 设置密钥参数。如果采用的是RC2RC4等流加密算法,这一步可以省略。如果采用的是分组加密算法,那应该在这一步设置加密模式等参数。比如
      CryptSetKeyParam(hKey, KP_MODE, CRYPT_MODE_CBC, 0);//设置成CBC模式
      CryptSetKeyParam(hKey, KP_IV, pbIV, 0);//设置初始向量

    BOOL CryptEncrypt(
    HCRYPTKEY hKey,
    HCRYPTHASH hHash,
    BOOL Final,
    DWORD dwFlags,
    BYTE *pbData,
    DWORD *pdwDataLen,
    DWORD dwBufLen);

    • 调用CryptEncrypt进行加密。hHash可以传NULL,除非加密的同时还要对原数据进行摘要运算;我们可以多次调用CryptEncrypt对原文分块进行加密,因此参数Final为true时表示没有分块加密或当前是最后一块加密,否则为false。要注意的是,这里的分块和分组加密里的分组是不同的概念,分组是加密算法本身的处理过程,而这里的分块是调用加密功能的业务逻辑,它们处于不同的层面。但分块长度必须是分组长度的整数倍;dwFlags传0;pbData传原文,调用后输出密文;pdwDataLen为要加密原文长度,调用后返回密文长度;dwBufLen是为pbData分配的缓冲区长度,在采用分组加密的情况先,密文长度会比明文长度长一些,所以dwBufLen的值应该设置的足够大,以满足返回加密结果的要求。一般的做法是调用两次CryptEncrypt,第一次调用时pbData传NULL,dwBufLen传0,调用后pdwDataLen输出密文所需长度;第二次调用时 dwBufLen设置的值不小于第一次调用后pdwDataLen即可。
      此方法同样调用成功返回true,否则返回false,并可以调用GetLastError返回具体错误信息。

    三、 对称解密

    对称解密与加密相对应,调用顺序和参数设置基本一致。

    CryptAcquireContext
    CryptCreateHash
    CryptHashData
    CryptDeriveKey
    CryptSetKeyParam
    BOOL CryptDecrypt(
    HCRYPTKEY hKey,
    HCRYPTHASH hHash,
    BOOL Final,
    DWORD dwFlags,
    BYTE *pbData,
    DWORD *pdwDataLen
    )
    此方法前四个参数意义与CryptEncrypt相同;pbData输入密文,调用后输出明文;pdwDataLen输入为密文长度,调用后输出明文长度。此方法返回值与CryptEncrypt一致。
    另外,对同一数据的加密和解密可以采用不同的分块方式。比如,加密时不分块,解密时分块,不影响最后的解密结果。

    PKCS#11

    一、架构

    image

    二、会话状态

    image

    三、对象

    image

    四、机制

    根据机制标记,可以分为几类:
    CKF_ENCRYPT:加密类
    CKF_DECRYPT:解密类
    CKF_DIGEST:摘要类
    CKF_SIGN:签名类
    CKF_SIGN_RECOVER:可恢复签名类
    CKF_VERIFY:验证类
    CKF_VERIFY_RECOVER:可恢复验证类
    CKF_GENERATE:密钥产生
    CKF_GENERATE_KEY_PAIR:密钥对产生
    CKF_WRAP:密钥封装
    CKF_UNWRAP:密钥解封
    CKF_DERIVE:密钥派生
    image

    五、操作

    image

    六、调用流程

    image

    GMT 0016-2012

    参考文献:
    https://max.book118.com/html/2018/0915/7164051200001146.shtm

    下列缩略语适用于本部分:
    ECC:椭圆曲线算法(Elliptic Curve Cryptography)
    IPK:内部加密公钥(Internal Public Key)
    ISK:内部加密私钥(Internal Private Key)
    EPK:外部加密公钥(External Public Key)
    KEK:密钥加密密钥(Key Encrypt Key)

    GM/T 0006设备定义信息如下:
    image
    实际数字结构定义:
    typedef struct DeviceInfo_st{
    unsigned char IssuerName[40];
    unsigned char DeviceName[16];
    unsigned char DeviceSerial[16];
    unsigned int DeviceVersion;
    unsigned int StandardVersion;
    unsigned int AsymAlgAbility[2];
    unsigned int SymAlgAbility;
    unsigned int HashAlgAbility;
    unsigned int BufferSize;
    }DEVICEINFO;

    GB/T 0018-2012
    **# define RSAref_MAX_BIT S2048
    **# define RSAref_MAX_LEN
    ((RSAref_MAX_BITS+7)/8)
    **# define RSAref_MAX_PBITS
    ((RSAref_MAX_BITS+1)/2)
    **#define RSAref_MAX_PLEN
    ((RSAref_MAX_PBITS+7)/8)
    typedef struct RSArefPublicKey_st
    unsigned int bits;
    unsigned char m[RSAref_MAX_LEN];
    unsigned char e[RSAref_MAX_LEN];
    }RSArefPublicKey;
    typedef struct RSArefPrivateKey_st
    {
    unsigned int bits;
    unsigned char m[RSAref_MAX_LEN];
    unsigned char e[RSAref_MAX_LEN];
    unsigned char d[RSAref_MAX_LEN];
    unsigned char prime[2][RSAref_MAX_PLEN]; unsigned char pexp[2][RSAref_MAX_PLEN]; unsigned char coef RSArefMAX_PLEN];
    }RSArefPrivateKey;

    ECC加密如下:
    image

    //******************************************
    //设备管理
    //******************************************
    /*
    功能:打开密码设备,返回设备句柄。
    参数:
    返回值:0(SDR OK) 成功
    非0 失败,返回错误代码
    /
    int SDF_OpenDevice(void
    * phDeviceHandle);
    /*
    功能:关闭密码设备,并释放相关资源。
    参数:hDeviceHandle[in] 已打开的设备句柄
    返回值:0(SDR OK) 成功
    非0 失败,返回错误代码
    /
    int SDF_CloseDevice(void
    hDeviceHandle);
    /*
    功能: 创建与密码设备的会话。
    已打开的设备句柄
    hDeviceHandlein]参数:h
    phessionHiandle[out]
    返回与密码设备建立的新会话句柄成功
    返回值:0(SDR OK) 成功
    非0 失败,返回错误代码
    /
    int SDF_OpenSession(void
    hDeviceHandle, void** phSessionHandle);

    /*
    功能:关闭与密码设备已建立的会话,并释放相关资源。
    参数:hSessionHandle[in] 与密码设备已建立的会话句柄
    返回值:0(SDR OK) 成功
    非0 失败,返回错误代码
    /
    int SDF_CloseSesson(void
    hSessionHandle);

    /*
    功能:获取密码设备能力描述。
    参数:hSessionHandle[in] 与设备建立的会话句柄
    pstDevicelnfo[our] 设备能力描述信息,内容及格式见设备信息定义成功
    返回值:0(SDR OK) 成功
    非0 失败,返回错误代码
    /
    int SDF_GetDeviceInfo(
    void
    hSessionHandle,
    DEVICEINFO* pstDeviceInfo);

    /*
    功能:获取指定长度的随机数。
    参数:
    hSessonHandle[in] 与设备建立的会话句柄
    uiLegth[in] 欲获取的随机数长度
    pucRandom[out] 缓冲区指针,用于存放获取的随机数
    返回值:0(SDR OK) 成功
    非0 失败,返回错误代码
    /
    int SDF_GenerateRandom(
    void
    hSessionHandle,
    unsigned int uiLength,
    unsigned char* pucRandom);

    /*
    功能:获取密码设备内部存储的指定索引私钥的使用权。
    参数:
    hSessionHandle[in] 与设备建立的会话句柄
    uiKeyIndex[in] 密码设备存储私钥的索引值
    pucPassword[in] 使用私钥权限的标识码
    uiPwdLength[in] 私钥访问控制码长度,不少于8 字节
    返回值:0(SDR OK) 成功
    非0 失败,返回错误代码
    /
    int SDF_GetPrivateKeyAccessRight(
    void
    hSessionHandle,
    unsigned int uiKeyIndex,
    unsigned char* pucPassword,
    unsigned int uiPwdLength);

    /*
    功能:释放密码设备存储的指定索引私钥的使用授权。
    参数:
    hSessonHandle[in] 与设备建立的会话句柄
    uiKeyIndex[in] 密码设备存储私钥索引值成功
    返回值∶0(SDR OK) 成功
    非0 失败,返回错误代码
    /
    int SDF_ReleasePrivateKeyAccessRight(
    void
    hSessionHandle,
    unsigned int uiKeyIndex);

           
           
           
           
           
           
           
           
           
           
           

  • 相关阅读:
    easyui改变tab标题
    java获取request中的参数、java解析URL问号后的参数
    java生成word文档
    jquery即时获取上传文件input file文件名
    微信公众号开发(三)
    Linux中文乱码 更改Linux字符集
    微信公众号开发(五)
    NSCache
    MIT神技术绘制用户界面至任意物体
    导弹工厂到摩托车间:制造业如何应用大数据
  • 原文地址:https://www.cnblogs.com/liang20181208/p/14646987.html
Copyright © 2020-2023  润新知