#java #cryptography #aes #rsa #bouncycastle
Вопрос:
tldr:
Есть ли способ создать данные в оболочке CMS, когда у меня уже есть зашифрованный контент с помощью AES и секретного ключа, который уже зашифрован открытым ключом?
Длинная версия:
У меня есть приложение, которое шифрует и расшифровывает данные с помощью AES (режим CBC и GCM). Симметричный ключ шифруется/дешифруется с помощью пар ключей RSA. Когда пользователь запрашивает данные, мы расшифровываем их в бэкэнде (Java) и отправляем в браузер
Обычно у нас есть открытый ключ и закрытый ключ, но в некоторых случаях требуется, чтобы у нас не было закрытого ключа, и расшифровка должна происходить в браузере (пользователь предоставляет PFX приватный ключ). Решением для этого является PKI.js который может расшифровывать данные с помощью PFX и данных в оболочке CMS.
Проблема в том, что мы уже зашифровали данные и не имеем доступа к обычным данным, которые мы можем использовать для создания данных в оболочке CMS.
Редактировать: @dave_thompson_085 спасибо за ответ! У меня есть следующий вопрос. Я не храню сертификаты в системе, поэтому единственное, что у меня есть, — это открытый ключ. Есть ли способ настроить ваш код в соответствии с этим требованием?
До вашего ответа я шифровал данные во второй раз только для объекта, окутанного CMS. В этом коде я использовал только открытый ключ для генерации получателей. Есть ли способ настроить ваш код так, чтобы он генерировал ответ только с открытым ключом? Мой предыдущий код:
SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
OAEPParameterSpec oaepSpec = new OAEPParameterSpec(digest, "MGF1", new MGF1ParameterSpec(mgfDigest), PSource.PSpecified.DEFAULT);
AlgorithmIdentifier oaepAlgId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, oaepSpec);
RecipientInfoGenerator recipientInfoGenerator = new JceKeyTransRecipientInfoGenerator(subjectKeyIdentifier.getEncoded(),oaepAlgId,publicKey).setProvider("BC");
envelopedDataGenerator.addRecipientInfoGenerator(recipientInfoGenerator);
А как насчет алгоритма хэширования? Нужна ли она мне или это только дополнительная защита, чтобы гарантировать, что объект, окруженный CMS, не изменился?
Ответ №1:
FWIW вы можете использовать BouncyCastle только для форматирования DER (плюс установка версий, незначительное удобство), плюс PEM, если вы этого хотите (также незначительное удобство), после того, как вы выполните всю остальную работу самостоятельно. Пример:
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.security.spec.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
// sample data and encryption, replace as needed
byte[] input = "testdata".getBytes();
X509Certificate cert = null;
try(InputStream is = new FileInputStream(args[0])){
cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
}
byte[] skey = new byte[16], snonce = new byte[16];
SecureRandom rand = new SecureRandom(); rand.nextBytes(skey); rand.nextBytes(snonce);
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(skey,"AES"), new IvParameterSpec(snonce));
byte[] ctx1 = aes.doFinal(input);
Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsa.init(Cipher.ENCRYPT_MODE, cert.getPublicKey());
byte[] ctx2 = rsa.doFinal(skey);
// now build the message
byte[] issuer = cert.getIssuerX500Principal().getEncoded();
ASN1Set recips = new DERSet( new KeyTransRecipientInfo(
new RecipientIdentifier(
new IssuerAndSerialNumber(X500Name.getInstance(issuer),cert.getSerialNumber() )),
new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption),
new DEROctetString(ctx2) ));
EnvelopedData env = new EnvelopedData (null/*no originfo*/, recips,
new EncryptedContentInfo(CMSObjectIdentifiers.data,
new AlgorithmIdentifier(NISTObjectIdentifiers.id_aes128_CBC,
new DEROctetString(snonce) ),
new DEROctetString(ctx1) ),
new DERSet() /*no attributes*/ );
ContentInfo msg = new ContentInfo(CMSObjectIdentifiers.envelopedData, env);
try(OutputStream os = new FileOutputStream(args[1]) ){
os.write(msg.getEncoded());
}
// or use PemWriter (and a PemObject) if you want PEM
Комментарии:
1. Большое вам спасибо! У меня есть дополнительные вопросы, так что не могли бы вы, пожалуйста, взглянуть?
Ответ №2:
Хорошо, я понял, как настроить код в соответствии с моими потребностями, чтобы я мог поделиться.
Создание получателей:
public RecipientInfo generateRecipientInfo(){
try{
SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
RecipientIdentifier recipId = new RecipientIdentifier(new DEROctetString(subjectKeyIdentifier));
return new RecipientInfo(new KeyTransRecipientInfo(recipId, getOAEPAlgorithmIdentifier(),
new DEROctetString(encryptedOaepKey)));
}catch(Exception e){
return null;
}
}
Алгоритм идентификации для RSA-OAEP:
private AlgorithmIdentifier getOAEPAlgorithmIdentifier(){
try{
String digest = "SHA-1";
String mgfDigest = "SHA-1";
JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
OAEPParameterSpec oaepSpec = new OAEPParameterSpec(digest, "MGF1", new MGF1ParameterSpec(mgfDigest), PSource.PSpecified.DEFAULT);
AlgorithmIdentifier oaepAlgId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, oaepSpec);
return oaepAlgId;
}catch (Exception e){
return null;
}
}
Идентификатор алгоритма для AES CBC (или GCM также после изменения алгоритма CMSAlgorithm.*)
public AlgorithmIdentifier getAlgorithmIdentifier() {
try{
AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("AES","BC");
algorithmParameters.init(new IvParameterSpec(new byte[keyLength/8]));
return new AlgorithmIdentifier(CMSAlgorithm.AES128_CBC, AlgorithmParametersUtils.extractParameters(algorithmParameters));
}catch (Exception e){
return null;
}
}
И, наконец, создание обернутого объекта CMS:
public CMSEnvelopedData generate() throws CMSException {
ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
AlgorithmIdentifier encAlgId = aesCryptography.getAlgorithmIdentifier();;
ASN1OctetString encContent = new BEROctetString(encryptedContent);;
recipientInfos.add(generateRecipientInfo());
EncryptedContentInfo eci = new EncryptedContentInfo(
CMSObjectIdentifiers.data,
encAlgId,
encContent);
ContentInfo contentInfo = new ContentInfo(
CMSObjectIdentifiers.envelopedData,
new EnvelopedData(null, new DERSet(recipientInfos), eci, (ASN1Set)null));
return new CMSEnvelopedData(contentInfo);
}