Как читать закрытый ключ для использования с OpenSAML?
ОК, это еще один из тех вопросов "Я не знаю, с чего начать", поэтому, надеюсь, ответ прост. Тем не менее, я действительно не знаю, что искать, и мои попытки до сих пор не очень полезны.
Я хочу прочитать закрытый ключ из файла (в настоящее время на диске). В конечном итоге ключ будет находиться в базе данных, но на данный момент это будет достаточно хорошим, и эта разница не должна иметь никакого реального отношения к разбору ключевого материала. Я смог создать экземпляр 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
для подписи данных?
Ответы
Ответ 1
BasicX509Credential
не является частью стандартного Java; Я полагаю, вы говорите о org.opensaml.xml.security.x509.BasicX509Credential
из OpenSAML.
Вам нужен 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 бит слабы; 512-битный ключ RSA был нарушен в 1999 году с несколькими сотнями компьютеров. В 2011 году, с 12-летними технологическими достижениями, следует предположить, что 512-битный ключ RSA может быть нарушен почти кем угодно. Поэтому используйте 1024-битные RSA-ключи, по крайней мере (желательно 2048 бит, вычислительные накладные расходы при использовании ключа не так уж плохи, вы все равно сможете выполнить сто подписей в секунду).
Ответ 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.
Спасибо,
Yogesh