如何读取用于OpenSAML的私钥?

12

好的,这是又一个那种“我真的不知道从哪里开始”的问题,希望答案很简单。然而,我不知道要搜索什么,到目前为止我的尝试也没有找到太多有用的东西。

我想从一个(当前在磁盘上)文件中读取私钥。最终该密钥将驻留在数据库中,但就解析密钥材料而言,这应该已经足够了,因此这种差异不会产生任何实际影响。我已经能够创建一个包含密钥公共部分的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 实例对数据进行签名?

2个回答

21

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恰好是该格式的封装),并将其编码为带有头部和尾部的Base64 PEM格式。这两个特征都不受纯Java支持,因此需要转换为PKCS#8。选项-nocrypt是因为PKCS#8支持对私钥进行可选基于密码的加密。

警告:你真的非常非常想要使用更长的RSA密钥。512位是弱的;一个512位的RSA密钥在1999年被破解了,仅仅用了几百台计算机。2011年,随着12年的技术进步,人们应该认为几乎任何人都可以破解512位的RSA密钥。因此,至少要使用1024位的RSA密钥(最好是2048位)。当使用密钥时,计算开销并不那么糟糕,您仍然能够执行数百个签名每秒。


是的,BasicX509Credential来自OpenSAML,对此疏忽深感抱歉。我一定会尝试一下。是的,我非常清楚512位RSA密钥绝不安全,但这个特定的设置严格用于尝试让所有东西正常工作,因此密钥长度不是问题。 - user
看起来非常顺利,非常感谢!当然,我的签名代码似乎有问题,但至少根据调试器,我从磁盘上的两个文件中获得了正确的“凭证”。继续进行... - user
谢谢。我在Windows上重定向<和>时遇到了神秘的问题,因此可能需要用-in和-out开关替换它们。 - Pavel Vlasov

1
这个问题与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。

谢谢, Yogesh


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接