【问题标题】:How to read a private key for use with OpenSAML?如何读取用于 OpenSAML 的私钥?
【发布时间】:2011-03-08 10:05:40
【问题描述】:

好的,这是另一个“我不知道从哪里开始”的问题,所以希望答案很简单。但是,我真的不知道要搜索什么,而且到目前为止我的尝试也没有多大用处。

我想从(当前在磁盘上的)文件中读取私钥。最终,密钥将驻留在数据库中,但目前这已经足够了,并且这种差异对解析密钥材料没有真正的影响。我已经能够创建一个 Credential 实例来保存密钥的公共部分(由调试器确认),但我似乎无法弄清楚如何读取私有部分。密钥对生成为:

openssl genrsa 512 > d:\host.key
openssl req -new -x509 -nodes -sha1 -days 365 -key d:\host.key > d:\host.cert

是的,我知道 512 位 RSA 密钥很久以前就被破解了。但是,为了让 API 正常工作,我认为没有理由不必要地耗尽系统熵供应。)

到目前为止的代码是:

import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.x509.BasicX509Credential;

private Credential getSigningCredential()
throws java.security.cert.CertificateException, IOException {
    BasicX509Credential credential = new BasicX509Credential();

    credential.setUsageType(UsageType.SIGNING);

    // read public key
    InputStream inStream = new FileInputStream("d:\\host.cert");
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
    inStream.close();
    credential.setEntityCertificate(cert);

    // TODO: read private key

    // done.
    return credential;
}

但是如何将文件host.key读入credential的私钥部分,从而可以使用生成的Credential实例对数据进行签名呢?

【问题讨论】:

    标签: java cryptography opensaml


    【解决方案1】:

    BasicX509Credential 不是标准 Java 的一部分;我想您是在谈论来自 OpenSAML 的 org.opensaml.xml.security.x509.BasicX509Credential

    您想要一个PrivateKey,您将使用credential.setPrivateKey() 设置它。要得到PrivateKey,首先要把私钥转换成Java可以读取的格式,即PKCS#8:

    openssl pkcs8 -topk8 -nocrypt -outform DER < D:\host.key > D:\host.pk8
    

    然后,来自 Java:

    RandomAccessFile raf = new RandomAccessFile("d:\\host.pk8", "r");
    byte[] buf = new byte[(int)raf.length()];
    raf.readFully(buf);
    raf.close();
    PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(buf);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    PrivateKey privKey = kf.generatePrivate(kspec);
    

    瞧!你有你的PrivateKey

    默认情况下,openssl 以自己的格式写入密钥(对于 RSA 密钥,PKCS#8 恰好是该格式的包装器),并且它将它们编码在 PEM 中,Base64 与页眉和页脚。纯 Java 不支持这两个特性,因此转换为 PKCS#8。 -nocrypt 选项是因为 PKCS#8 支持可选的基于密码的私钥加密。

    警告:您真的真的想要使用更长的 RSA 密钥。 512位弱; 1999 年,数百台计算机破解了 512 位 RSA 密钥。 2011 年,经过 12 年的技术进步,人们应该假设 512 位 RSA 密钥几乎可以被任何人破解。因此,至少使用 1024 位 RSA 密钥(最好是 2048 位;使用密钥时的计算开销还不错,您仍然可以每秒执行数百个签名)。

    【讨论】:

    • 是的,BasicX509Credential 来自 OpenSAML,对此疏忽感到抱歉。我一定会试一试的。是的,我非常清楚 512 位 RSA 密钥绝不是安全的,但是这种特殊设置严格是为了让一切正常工作,所以密钥长度不是问题。
    • 看起来很有魅力,非常感谢!当然,我的签名代码似乎被破坏了,但至少根据调试器,我从磁盘上的两个文件中得到了正确的Credential。继续巡演……
    • 谢谢。我在 Windows 上重定向 时遇到了神秘的麻烦,因此可能想用 -in 和 -out 开关替换它们。
    【解决方案2】:

    这个问题与 SAML 相关,也与想要检索用于签署 XMLObject 的私钥的人相关。上面的答案很好用,也可以从密钥库中检索私钥:

            Properties sigProperties = new Properties();
    
        sigProperties.put("org.apache.ws.security.crypto.provider","org.apache.ws.security.components.crypto.Merlin");
        sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.type","jks");
        sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.password","keypass");
        sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.alias","keyalias");
        sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.file","keystore.jks");
    
        Crypto issuerCrypto = CryptoFactory.getInstance(sigProperties);
    
        String issuerKeyName = (String) sigProperties.get("org.apache.ws.security.crypto.merlin.keystore.alias");
    
        //See http://ws.apache.org/wss4j/xref/org/apache/ws/security/saml/ext/AssertionWrapper.html 'signAssertion' method
        // prepare to sign the SAML token
        CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
        cryptoType.setAlias(issuerKeyName);
        X509Certificate[] issuerCerts = issuerCrypto.getX509Certificates(cryptoType);
        if (issuerCerts == null) {
            throw new WSSecurityException(
                    "No issuer certs were found to sign the SAML Assertion using issuer name: "
                            + issuerKeyName);
        }
    
        String password = ADSUnitTestUtils.getPrivateKeyPasswordFromAlias(issuerKeyName);
    
        PrivateKey privateKey = null;
        try {
            privateKey = issuerCrypto.getPrivateKey(issuerKeyName, password);
        } catch (Exception ex) {
            throw new WSSecurityException(ex.getMessage(), ex);
        }
    
    
        BasicX509Credential signingCredential = new BasicX509Credential();
        signingCredential.setEntityCertificate(issuerCerts[0]);
        signingCredential.setPrivateKey(privateKey);
    
        signature.setSigningCredential(signingCredential);
    

    这比请求的原始查询更多代码,但看起来他们正在尝试获取 BasicX509Credential。

    谢谢, 尤格什

    【讨论】:

      猜你喜欢
      • 2021-09-22
      • 2010-09-19
      • 2016-12-07
      • 2012-08-01
      • 1970-01-01
      • 2013-07-13
      • 1970-01-01
      相关资源
      最近更新 更多