如何使用智能卡和 PKCS#11 重用现有私钥生成数字签名

How to reuse existing private key to generate a digital signature using smartcard and PKCS#11

我想使用 pkcs11 标准创建数字签名。 假设我已经有一个 public 和私钥对存储在我的智能卡上。 此密钥是使用下一个代码生成的:

byte[] ckaId = session.GenerateRandom(20);

// Prepare attribute template of new public key
var publicKeyAttributes = new List<ObjectAttribute>();
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_TOKEN, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, false));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, PKCS11Settings.ApplicationName));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_ID, ckaId));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_ENCRYPT, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_VERIFY, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_VERIFY_RECOVER, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_WRAP, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_MODULUS_BITS, 1024));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_PUBLIC_EXPONENT, new byte[] { 0x01, 0x00, 0x01 }));

// Prepare attribute template of new private key
var privateKeyAttributes = new List<ObjectAttribute>();
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_TOKEN, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, PKCS11Settings.ApplicationName));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_ID, ckaId));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_SENSITIVE, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_DECRYPT, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_SIGN, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_SIGN_RECOVER, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_UNWRAP, true));

// Specify key generation mechanism
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS_KEY_PAIR_GEN);

// Generate key pair
session.GenerateKeyPair(mechanism, publicKeyAttributes, privateKeyAttributes, out publicKeyHandle, out privateKeyHandle);

现在我可以使用这些密钥来签署一些数据。例如:

var mechanism = new Mechanism(CKM.CKM_RSA_PKCS);
byte[] byteContent = (ConvertUtils.Utf8StringToBytes("Hello World!!!"));
byte[] signature = session.Sign(mechanism, derivedKey, byteContent);

当您想创建密钥然后在 C_sign 方法

中使用它时,此代码非常有效

但是如何获得对已经存在的密钥的访问权限来执行类似的操作? 据我了解,我应该使用 C_Derrive() 方法从现有私钥中派生私钥,而不是在 C_Sign() 方法中使用它。 为此,我编写了下一个代码:

// Prepare attribute template of new key
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_SECRET_KEY));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES3));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_ENCRYPT, true));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_DECRYPT, true));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_DERIVE, true));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_EXTRACTABLE, true));

// Specify key generation mechanism
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS);

// Generate key
ObjectHandle baseKey = session.GenerateKey(mechanism, objectAttributes);

byte[] dt = session.GenerateRandom(24);

// Specify mechanism parameters
var mechanismParams = new CkKeyDerivationStringData(dt);

// Specify derivation mechanism with parameters
Mechanism mech = new Mechanism(CKM.CKM_RSA_PKCS, mechanismParams);

// Derive key
ObjectHandle derivedKey = session.DeriveKey(mech, baseKey, null);


byte[] byteContent = (ConvertUtils.Utf8StringToBytes("Hello World!"));
byte[] signature = session.Sign(mech, derivedKey, byteContent);

但是当我 运行 这段代码时它会抛出下一个错误:

Method C_GenerateKey returned CKR_MECHANISM_INVALID

任何人都可以告诉我我做错了什么以及如何解决这个问题吗?

如果你想获得 ObjectHandle 一个现有的密钥,你需要 找到 通过它的属性如标签等密钥。密钥推导是完全不同的密码操作.

以下代码示例搜索您问题中的代码生成的密钥:

// Prepare attribute template that defines search criteria for public key
List<ObjectAttribute> publicKeyAttributes = new List<ObjectAttribute>();
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PUBLIC_KEY));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_RSA));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, PKCS11Settings.ApplicationName));

// Find all objects that match provided attributes
List<ObjectHandle> foundPublicKeys = session.FindAllObjects(publicKeyAttributes);
if (foundPublicKeys == null || foundPublicKeys.Count != 1)
    throw new Exception("Unable to find public key");

// Prepare attribute template that defines search criteria for private key
List<ObjectAttribute> privateKeyAttributes = new List<ObjectAttribute>();
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_RSA));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, PKCS11Settings.ApplicationName));

// Find all objects that match provided attributes
List<ObjectHandle> foundPrivateKeys = session.FindAllObjects(publicKeyAttributes);
if (foundPrivateKeys == null || foundPrivateKeys.Count != 1)
    throw new Exception("Unable to find private key");

// Use found object handles
ObjectHandle publicKeyHandle = foundPublicKeys[0];
ObjectHandle privateKeyHandle = foundPrivateKeys[0];

首先,我想我会引用 pkcs-11v2-20.pdf 几次,如果还没有副本,请抓住它(它也带有有用的示例)。

其次,我不是 C# 程序员,所以很遗憾,以下内容只是伪代码。

让我们首先解决 CKR_MECHANISM_INVALID 问题: 根据标准,CKM.CKM_RSA_PKCS 不能用于 C_DeriveKey(第 12 章,Table 34)。

现在手头的问题是: 您的智能卡上已经有一对密钥(并打开了一个会话并按要求登录),您必须搜索您需要使用的内容 C_FindObjectsInit、C_FindObjects 和 C_FindObjectsFinal(第 136 页及以下,还给出了一些示例), 你在哪里提供 C_FindObjectsInit 你正在寻找什么样的密钥的属性模板,例如

// look for key allowing signing and decrypting
var searchCriteria = new List<ObjectAttribute>();
searchCriteria.Add(new ObjectAttribute(CKA.CKA_DECRYPT, true));
searchCriteria.Add(new ObjectAttribute(CKA.CKA_SIGN, true));

// initialize the search. The number is actually the number of search attributes.
session.FindObjectsInit(searchCriteria, 2);
...
session.FindObjects(out privateKeyHandle, ...);
... 
session.FindObjectsFinal();

// we found the requested private key, now sign the message
session.Sign(..., privateKeyHandle,...);

您在最后一个片段中尝试的密钥推导适用于智能卡和应用程序需要通过非对称加密法推导一个或两个共享密钥(例如用于安全消息传递)的场景。

希望对您有所帮助。