C# 中 PKCS11Interop 库的线程安全使用
Threadsafe usage of PKCS11Interop library in C#
我正在使用 PKCS11Interop 在 HSM 中执行密钥管理操作。我使用的 HSM 是 Thales PCI Express。下面是包装在 HSM 中执行的所有操作的 class:
public sealed class KeyStoreOperations
{
private KeyStoreContext m_keyStoreContext;
private static Pkcs11 m_Pkcs11;
private static readonly object _syncLockPkcs11 = new object();
private static readonly object _syncLockHSMLogin = new object();
public KeyStoreOperations(KeyStoreContext keyStoreContext)
{
m_keyStoreContext = keyStoreContext;
InitializePkcs11Object();
}
/// <summary>
/// Generates key in the key store
/// </summary>
/// <param name="keyName"></param>
/// <returns></returns>
public void GenerateKey(string keyName)
{
HSMTransactionHandler((Session session) =>
{
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS_KEY_PAIR_GEN);
ObjectHandle publicKeyHandle = null;
ObjectHandle privateKeyHandle = null;
byte[] ckaId = session.GenerateRandom(20);
List<ObjectAttribute> publicKeyAttributes = CreatePublicKeyTemplate(keyName, ckaId);
List<ObjectAttribute> privateKeyAttributes = CreatePrivateKeyTemplate(keyName, ckaId);
session.GenerateKeyPair(mechanism, publicKeyAttributes, privateKeyAttributes, out publicKeyHandle, out privateKeyHandle);
});
}
/// <summary>
/// Destroys key in the key store
/// </summary>
/// <param name="keyLabel"></param>
/// <returns></returns>
public void DestroyKey(string keyName)
{
HSMTransactionHandler((Session session) =>
{
var publicKeyHandle = GetPublicKey(keyName, session);
var privateKeyHandle = GetPrivateKey(keyName, session);
if (publicKeyHandle != null && privateKeyHandle != null)
{
session.DestroyObject(publicKeyHandle);
session.DestroyObject(privateKeyHandle);
}
});
}
/// <summary>
/// Encrypts a message using the key in the key store
/// </summary>
/// <param name="keyName"></param>
/// <param name="message"></param>
/// <returns></returns>
public string Encrypt(string keyName, string message)
{
ValidateInputs(message, "Message");
var encryptedMessage = string.Empty;
HSMTransactionHandler((Session session) =>
{
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS);
var publicKey = GetPublicKey(keyName, session);
if (publicKey == null)
throw new HSMException(ErrorConstant.HSM_ENCRYPTION_KEY_NOT_FOUND);
var originalKeyBytes = EncryptionHelper.Decode(message);
var encryptedKeyBytes = session.Encrypt(mechanism, publicKey, originalKeyBytes);
encryptedMessage = EncryptionHelper.Encode(encryptedKeyBytes);
});
return encryptedMessage;
}
/// <summary>
/// Decrypts a key using the key in the key store
/// </summary>
/// <param name="keyName"></param>
/// <param name="cipher"></param>
/// <returns></returns>
public string Decrypt(string keyName, string cipher)
{
ValidateInputs(cipher, "Cipher");
var decryptedMessage = string.Empty;
HSMTransactionHandler((Session session) =>
{
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS);
var privateKey = GetPrivateKey(keyName, session);
if (privateKey == null)
throw new HSMException(ErrorConstant.HSM_ENCRYPTION_KEY_NOT_FOUND);
var encryptedSymmetricKeyBytes = EncryptionHelper.Decode(cipher);
var decryptedSymmetricKeyBytes = session.Decrypt(mechanism, privateKey, encryptedSymmetricKeyBytes);
decryptedMessage = EncryptionHelper.Encode(decryptedSymmetricKeyBytes);
});
return decryptedMessage;
}
#region Private methods
#region Validations
private void ValidateInputs(string input, string name)
{
if (string.IsNullOrEmpty(input))
throw new ArgumentNullException(name);
}
#endregion Validations
private void HSMTransactionHandler(Action<Session> action)
{
Slot hsmSlot = null;
Session hsmSession = null;
try
{
hsmSlot = GetSlot(m_keyStoreContext.ModuleToken);
hsmSession = hsmSlot.OpenSession(false);
lock (_syncLockHSMLogin)
{
hsmSession.Login(CKU.CKU_USER, m_keyStoreContext.SecurityPin);
action(hsmSession);
hsmSession.Logout();
}
}
catch (Pkcs11Exception ex)
{
HandleHSMErrors(ex);
}
finally
{
if (!(hsmSession == null))
hsmSession.CloseSession();
}
}
private ObjectHandle GetPrivateKey(string keyName, Session session)
{
ObjectHandle privateKey = null;
List<ObjectHandle> foundObjects = null;
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, keyName));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, true));
foundObjects = session.FindAllObjects(objectAttributes);
if (foundObjects != null && foundObjects.Count > 0)
{
privateKey = foundObjects[0];
}
return privateKey;
}
private ObjectHandle GetPublicKey(string keyName, Session session)
{
ObjectHandle publicKey = null;
List<ObjectHandle> foundObjects = null;
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, keyName));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, false));
foundObjects = session.FindAllObjects(objectAttributes);
if (foundObjects != null && foundObjects.Count > 0)
{
publicKey = foundObjects[0];
}
return publicKey;
}
private List<ObjectAttribute> CreatePublicKeyTemplate(string keyName, byte[] ckaId)
{
List<ObjectAttribute> 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, keyName));
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, Convert.ToUInt64(m_keyStoreContext.KeySize)));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_PUBLIC_EXPONENT, new byte[] { 0x01, 0x00, 0x01 }));
return publicKeyAttributes;
}
private List<ObjectAttribute> CreatePrivateKeyTemplate(string keyName, byte[] ckaId)
{
List<ObjectAttribute> 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, keyName));
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));
return privateKeyAttributes;
}
private Slot GetSlot(string tokenLabel)
{
Slot matchingSlot = null;
List<Slot> slots = m_Pkcs11.GetSlotList(true);
matchingSlot = slots[0];
if (tokenLabel != null)
{
matchingSlot = null;
foreach (Slot slot in slots)
{
TokenInfo tokenInfo = null;
try
{
tokenInfo = slot.GetTokenInfo();
}
catch (Pkcs11Exception ex)
{
if (ex.RV != CKR.CKR_TOKEN_NOT_RECOGNIZED && ex.RV != CKR.CKR_TOKEN_NOT_PRESENT)
throw;
}
if (tokenInfo == null)
continue;
if (!string.IsNullOrEmpty(m_keyStoreContext.ModuleToken))
if (0 != string.Compare(m_keyStoreContext.ModuleToken, tokenInfo.Label, StringComparison.Ordinal))
continue;
matchingSlot = slot;
break;
}
if (matchingSlot == null)
throw new HSMException(string.Format(ErrorConstant.HSM_CONFIGURATION_ERROR_INCORRECT_SLOT, tokenLabel));
}
return matchingSlot;
}
private void InitializePkcs11Object()
{
if (m_Pkcs11 == null)
{
lock (_syncLockPkcs11)
{
m_Pkcs11 = new Pkcs11(m_keyStoreContext.PKCS11LibraryPath, true);
}
}
}
private void HandleHSMErrors(Pkcs11Exception ex)
{
if (ex.RV == CKR.CKR_PIN_INCORRECT)
{
throw new HSMException(ErrorConstant.HSM_CONFIGURATION_ERROR_PIN_INCORRECT, ex);
}
else
{
throw new HSMException(ErrorConstant.HSM_CONFIGURATION_ERROR_GENERIC, ex);
}
}
#endregion
}
如果您注意到我正在使用两个对象来应用锁。对象 _syncLockPkcs11 用于在 m_Pkcs11 上实现单例,而 _syncLockHSMLogin 用于将登录同步到 HSM。早些时候,当我没有这些锁时,我常常从 HSM 得到以下错误,CKU_USER_ALREADY_LOGGED_IN 和 CKR_FUNCTION_FAILED。我根据此 link and in the section 6.7.7 Example of use of sessions of this document i.e.
中提供的信息实施了此更改
在我目前的实施中,我没有遇到任何这些错误,但想在这里了解专家意见。
我的一些问题是:
以这种方式使用 m_Pkcs11 是否可以,即不在整个进程生命周期中处理它?
是否可以对 HSM 登录方法应用锁定?我问是因为我没有找到任何在线参考建议。
有没有更好的方法来实现这个目标?
您所有问题的答案都在 PKCS#11 v2.20 specificiation 中 "hidden"。
请参阅第 6.6 章,了解有关在整个过程生命周期中不处置 m_Pkcs11
的更多信息:
An application becomes a "Cryptoki application" by calling the
Cryptoki function C_Initialize
(see Section 11.4) from one of its
threads; after this call is made, the application can call other
Cryptoki functions. When the application is done using Cryptoki, it
calls the Cryptoki function C_Finalize
(see Section 11.4) and ceases
to be a Cryptoki application.
换句话说,您只需要创建 Pkcs11
class 的实例一次,然后您的所有线程都可以访问 PKCS#11 函数。我见过确实使用 Pkcs11
class 的单个实例并且几个月都没有处理它的应用程序。这是一个完全有效的用法。
有关登录状态的更多信息,请参阅第 6.7.4 章:
In Cryptoki, all sessions that an application has with a token must
have the same login/logout status (i.e., for a given application and
token, one of the following holds: all sessions are public sessions;
all sessions are SO sessions; or all sessions are user sessions). When
an application's session logs into a token, all of that application's
sessions with that token become logged in, and when an application's
session logs out of a token, all of that application’s sessions with
that token become logged out.
换句话说,一旦您登录到一个会话,您也会登录所有现有会话,以及将来打开的所有会话。这就是您收到 CKU_USER_ALREADY_LOGGED_IN
错误的主要原因。我见过登录到单个会话并保持打开数月的应用程序。顺便说一句,你可以使用 Session::GetSessionInfo()
方法来检查你的会话是否已经登录。
有关 class 与您相似的真实示例,请查看 Pkcs11RsaSignature
class from Pkcs11Interop.PDF 项目。
我正在使用 PKCS11Interop 在 HSM 中执行密钥管理操作。我使用的 HSM 是 Thales PCI Express。下面是包装在 HSM 中执行的所有操作的 class:
public sealed class KeyStoreOperations
{
private KeyStoreContext m_keyStoreContext;
private static Pkcs11 m_Pkcs11;
private static readonly object _syncLockPkcs11 = new object();
private static readonly object _syncLockHSMLogin = new object();
public KeyStoreOperations(KeyStoreContext keyStoreContext)
{
m_keyStoreContext = keyStoreContext;
InitializePkcs11Object();
}
/// <summary>
/// Generates key in the key store
/// </summary>
/// <param name="keyName"></param>
/// <returns></returns>
public void GenerateKey(string keyName)
{
HSMTransactionHandler((Session session) =>
{
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS_KEY_PAIR_GEN);
ObjectHandle publicKeyHandle = null;
ObjectHandle privateKeyHandle = null;
byte[] ckaId = session.GenerateRandom(20);
List<ObjectAttribute> publicKeyAttributes = CreatePublicKeyTemplate(keyName, ckaId);
List<ObjectAttribute> privateKeyAttributes = CreatePrivateKeyTemplate(keyName, ckaId);
session.GenerateKeyPair(mechanism, publicKeyAttributes, privateKeyAttributes, out publicKeyHandle, out privateKeyHandle);
});
}
/// <summary>
/// Destroys key in the key store
/// </summary>
/// <param name="keyLabel"></param>
/// <returns></returns>
public void DestroyKey(string keyName)
{
HSMTransactionHandler((Session session) =>
{
var publicKeyHandle = GetPublicKey(keyName, session);
var privateKeyHandle = GetPrivateKey(keyName, session);
if (publicKeyHandle != null && privateKeyHandle != null)
{
session.DestroyObject(publicKeyHandle);
session.DestroyObject(privateKeyHandle);
}
});
}
/// <summary>
/// Encrypts a message using the key in the key store
/// </summary>
/// <param name="keyName"></param>
/// <param name="message"></param>
/// <returns></returns>
public string Encrypt(string keyName, string message)
{
ValidateInputs(message, "Message");
var encryptedMessage = string.Empty;
HSMTransactionHandler((Session session) =>
{
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS);
var publicKey = GetPublicKey(keyName, session);
if (publicKey == null)
throw new HSMException(ErrorConstant.HSM_ENCRYPTION_KEY_NOT_FOUND);
var originalKeyBytes = EncryptionHelper.Decode(message);
var encryptedKeyBytes = session.Encrypt(mechanism, publicKey, originalKeyBytes);
encryptedMessage = EncryptionHelper.Encode(encryptedKeyBytes);
});
return encryptedMessage;
}
/// <summary>
/// Decrypts a key using the key in the key store
/// </summary>
/// <param name="keyName"></param>
/// <param name="cipher"></param>
/// <returns></returns>
public string Decrypt(string keyName, string cipher)
{
ValidateInputs(cipher, "Cipher");
var decryptedMessage = string.Empty;
HSMTransactionHandler((Session session) =>
{
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS);
var privateKey = GetPrivateKey(keyName, session);
if (privateKey == null)
throw new HSMException(ErrorConstant.HSM_ENCRYPTION_KEY_NOT_FOUND);
var encryptedSymmetricKeyBytes = EncryptionHelper.Decode(cipher);
var decryptedSymmetricKeyBytes = session.Decrypt(mechanism, privateKey, encryptedSymmetricKeyBytes);
decryptedMessage = EncryptionHelper.Encode(decryptedSymmetricKeyBytes);
});
return decryptedMessage;
}
#region Private methods
#region Validations
private void ValidateInputs(string input, string name)
{
if (string.IsNullOrEmpty(input))
throw new ArgumentNullException(name);
}
#endregion Validations
private void HSMTransactionHandler(Action<Session> action)
{
Slot hsmSlot = null;
Session hsmSession = null;
try
{
hsmSlot = GetSlot(m_keyStoreContext.ModuleToken);
hsmSession = hsmSlot.OpenSession(false);
lock (_syncLockHSMLogin)
{
hsmSession.Login(CKU.CKU_USER, m_keyStoreContext.SecurityPin);
action(hsmSession);
hsmSession.Logout();
}
}
catch (Pkcs11Exception ex)
{
HandleHSMErrors(ex);
}
finally
{
if (!(hsmSession == null))
hsmSession.CloseSession();
}
}
private ObjectHandle GetPrivateKey(string keyName, Session session)
{
ObjectHandle privateKey = null;
List<ObjectHandle> foundObjects = null;
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, keyName));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, true));
foundObjects = session.FindAllObjects(objectAttributes);
if (foundObjects != null && foundObjects.Count > 0)
{
privateKey = foundObjects[0];
}
return privateKey;
}
private ObjectHandle GetPublicKey(string keyName, Session session)
{
ObjectHandle publicKey = null;
List<ObjectHandle> foundObjects = null;
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, keyName));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, false));
foundObjects = session.FindAllObjects(objectAttributes);
if (foundObjects != null && foundObjects.Count > 0)
{
publicKey = foundObjects[0];
}
return publicKey;
}
private List<ObjectAttribute> CreatePublicKeyTemplate(string keyName, byte[] ckaId)
{
List<ObjectAttribute> 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, keyName));
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, Convert.ToUInt64(m_keyStoreContext.KeySize)));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_PUBLIC_EXPONENT, new byte[] { 0x01, 0x00, 0x01 }));
return publicKeyAttributes;
}
private List<ObjectAttribute> CreatePrivateKeyTemplate(string keyName, byte[] ckaId)
{
List<ObjectAttribute> 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, keyName));
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));
return privateKeyAttributes;
}
private Slot GetSlot(string tokenLabel)
{
Slot matchingSlot = null;
List<Slot> slots = m_Pkcs11.GetSlotList(true);
matchingSlot = slots[0];
if (tokenLabel != null)
{
matchingSlot = null;
foreach (Slot slot in slots)
{
TokenInfo tokenInfo = null;
try
{
tokenInfo = slot.GetTokenInfo();
}
catch (Pkcs11Exception ex)
{
if (ex.RV != CKR.CKR_TOKEN_NOT_RECOGNIZED && ex.RV != CKR.CKR_TOKEN_NOT_PRESENT)
throw;
}
if (tokenInfo == null)
continue;
if (!string.IsNullOrEmpty(m_keyStoreContext.ModuleToken))
if (0 != string.Compare(m_keyStoreContext.ModuleToken, tokenInfo.Label, StringComparison.Ordinal))
continue;
matchingSlot = slot;
break;
}
if (matchingSlot == null)
throw new HSMException(string.Format(ErrorConstant.HSM_CONFIGURATION_ERROR_INCORRECT_SLOT, tokenLabel));
}
return matchingSlot;
}
private void InitializePkcs11Object()
{
if (m_Pkcs11 == null)
{
lock (_syncLockPkcs11)
{
m_Pkcs11 = new Pkcs11(m_keyStoreContext.PKCS11LibraryPath, true);
}
}
}
private void HandleHSMErrors(Pkcs11Exception ex)
{
if (ex.RV == CKR.CKR_PIN_INCORRECT)
{
throw new HSMException(ErrorConstant.HSM_CONFIGURATION_ERROR_PIN_INCORRECT, ex);
}
else
{
throw new HSMException(ErrorConstant.HSM_CONFIGURATION_ERROR_GENERIC, ex);
}
}
#endregion
}
如果您注意到我正在使用两个对象来应用锁。对象 _syncLockPkcs11 用于在 m_Pkcs11 上实现单例,而 _syncLockHSMLogin 用于将登录同步到 HSM。早些时候,当我没有这些锁时,我常常从 HSM 得到以下错误,CKU_USER_ALREADY_LOGGED_IN 和 CKR_FUNCTION_FAILED。我根据此 link and in the section 6.7.7 Example of use of sessions of this document i.e.
在我目前的实施中,我没有遇到任何这些错误,但想在这里了解专家意见。
我的一些问题是:
以这种方式使用 m_Pkcs11 是否可以,即不在整个进程生命周期中处理它?
是否可以对 HSM 登录方法应用锁定?我问是因为我没有找到任何在线参考建议。
有没有更好的方法来实现这个目标?
您所有问题的答案都在 PKCS#11 v2.20 specificiation 中 "hidden"。
请参阅第 6.6 章,了解有关在整个过程生命周期中不处置 m_Pkcs11
的更多信息:
An application becomes a "Cryptoki application" by calling the Cryptoki function
C_Initialize
(see Section 11.4) from one of its threads; after this call is made, the application can call other Cryptoki functions. When the application is done using Cryptoki, it calls the Cryptoki functionC_Finalize
(see Section 11.4) and ceases to be a Cryptoki application.
换句话说,您只需要创建 Pkcs11
class 的实例一次,然后您的所有线程都可以访问 PKCS#11 函数。我见过确实使用 Pkcs11
class 的单个实例并且几个月都没有处理它的应用程序。这是一个完全有效的用法。
有关登录状态的更多信息,请参阅第 6.7.4 章:
In Cryptoki, all sessions that an application has with a token must have the same login/logout status (i.e., for a given application and token, one of the following holds: all sessions are public sessions; all sessions are SO sessions; or all sessions are user sessions). When an application's session logs into a token, all of that application's sessions with that token become logged in, and when an application's session logs out of a token, all of that application’s sessions with that token become logged out.
换句话说,一旦您登录到一个会话,您也会登录所有现有会话,以及将来打开的所有会话。这就是您收到 CKU_USER_ALREADY_LOGGED_IN
错误的主要原因。我见过登录到单个会话并保持打开数月的应用程序。顺便说一句,你可以使用 Session::GetSessionInfo()
方法来检查你的会话是否已经登录。
有关 class 与您相似的真实示例,请查看 Pkcs11RsaSignature
class from Pkcs11Interop.PDF 项目。