【问题标题】:Signing documents with SunMSCAPI and suppressing the "Enter PIN" dialog使用 SunMSCAPI 签署文档并取消“输入 PIN”对话框
【发布时间】:2011-09-23 16:54:49
【问题描述】:

我正在开发一个使用证书令牌对文档进行签名的 java 代码。到目前为止,一切都很好,但我想禁止“输入密码”对话框,因为我正在存储用户的密码,所以他/她不需要每次都输入密码。这里真正的问题是这段代码将以批处理模式运行(没有用户交互)。我知道一旦键入,密钥可能在内存中,因此短时间内不需要再次键入。但我不能依赖它,我需要提供 PIN。这是我到目前为止的代码(它只是一个示例,它可能不完整也不工作):

protected KeyStore loadKeyStoreFromSmartCard()  {
  keyStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
  keyStore.load(null, null);
  return keyStore;
}

public void signDocument(byte[] conteudoParaAssinar, String certAlias) {
    char[] pass = (char[]) null;
    PrivateKey key = (PrivateKey) loadKeyStoreFromSmartCard.getKey(certAlias, pass);
    Certificate[] chain = loadKeyStoreFromSmartCard(true).getCertificateChain(certAlias);
    CertStore certsAndCRLs = CertStore.getInstance("Collection", new CollectionCertStoreParameters(Arrays.asList(chain)), "BC");
    X509Certificate cert = (X509Certificate) chain[0];
    CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
    gen.addSigner(key, cert, CMSSignedDataGenerator.DIGEST_SHA1);
    gen.addCertificatesAndCRLs(certsAndCRLs);
    CMSProcessable data = new CMSProcessableByteArray(conteudoParaAssinar);
    CMSSignedData signed = gen.generate(data, true, "SunMSCAPI");
    byte[] envHex = signed.getEncoded();
}

编辑

我听说过CryptSetProvParam PP_KEYEXCHANGE_PIN 女巫可能是解决方案,但我不知道如何从 java 调用它。我找到的所有示例都是针对 .net 的。

【问题讨论】:

    标签: java digital-signature digital-certificate


    【解决方案1】:

    MS CryptoAPI 根本不提供任何方法来指定 PIN。如果可能,您唯一的选择是从 CryptoAPI 切换到 PKCS#11 - PKCS#11 要求您以代码“登录”到设备并在代码中提供 PIN。

    更新:一些一些硬件供应商提供的 CSP(加密服务提供商)模块允许您调用特殊的 CryptoAPI 函数(CryptSetProvParam,https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptsetprovparam)并将 PIN 传递给它。如果您的硬件的 CSP 支持这种设置 PIN 的方法,您需要联系供应商获取信息,如果支持,确切的参数 ID 是什么等。无论如何,您目前无法在此设置 PIN使用标准 SunMSCAPI 提供程序从您的 Java 应用程序中获取的方式。

    【讨论】:

    • 可能没有办法注册回调?我不能使用 PKCS .. :(
    • 即使以下内容没有提供来自 Java 上下文的解决方案,可能值得一提的是,要调用的正确 MS CryptoAPI 函数似乎是 CryptSetKeyParam 而不是 CryptSetProvParam 所示在stackoverflow.com/a/495793/320594.
    • @JaimeHablutzel 在 SecureBlackbox 代码中,CryptSetProvParam 已成功使用多年。这并不意味着 CryptSetKeyParam 是错误的方法 - 我可以相信一些 CSP 接受 PIN 作为密钥参数而不是存储参数。
    • @EugeneMayevski'AlliedBits,Java SBB 中是否有任何 API 调用 CryptSetProvParam 以提供密钥密码并加载 java.security.PrivateKey?。
    • @JaimeHablutzel SecureBlackbox 使用它的类运行。它不会为您提供 PrivateKey 实例 IIRC,也不会使用 PrivateKey 进行操作。
    【解决方案2】:

    我曾经实现过类似的东西,但不幸的是智能卡驱动程序有问题,因此驱动程序有时会尝试调用在驱动程序本身中实现的本机 PIN 回调。但是让我们假设您的驱动程序在这方面做得更好。

    首先,你需要实现一个CallbackHandler,文档很好地概述了这个概念。在您的情况下,处理起来很有趣的是 PasswordCallback 情况。

    接下来,按如下方式创建您的KeyStore(省略异常处理)

    Provider provider = Security.getProvider("SunMSCAPI");
    CallbackHandler cbh = // your implementation
    KeyStore.ProtectionParameter protection = new KeyStore.CallbackHandlerProtection(cbh);
    //get a handle of the CAPI KeyStore as before
    KeyStore.Builder keystoreBuilder = KeyStore.Builder.newInstance("Windows-MY",
                                                                    provider, 
                                                                    protection);
    KeyStore store = keystoreBuilder.getKeyStore();
    

    然后,要访问私钥,请执行以下操作:

    KeyStore.Entry ke = store.getEntry(alias, null);
    if (!(ke instanceof KeyStore.PrivateKeyEntry))
        throw new RuntimeException("The entry is not a private key.");
    PrivateKey key = ((KeyStore.PrivateKeyEntry) ke).getPrivateKey();
    

    提供商将自动生成适当的PasswordCallbacks 以发送到您的CallbackHandler。处理回调时,您只需传递缓存的密码。

    不用说,密码缓存通常是不受欢迎的 ;)

    【讨论】:

    • 您是否测试过您提供的解决方案?我认为您没有这样做,因为 SunMSCAPI KeyStore 实现完全忽略了stackoverflow.com/a/495793/320594 中提供的密码。
    猜你喜欢
    • 2016-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多