I have an email attachment in .p7m format and a .pem file containing private keys and certificates.
Using OpenSSL I can decrypt the file with this command:
openssl smime -decrypt -inform DER -in fileToDecrypt.p7m -inkey privateKey.pem -out destinationFile
But using bouncycastle in Java, I could not decrypt it.
I read the private key with this code:
PEMReader pemReader = new PEMReader(new InputStreamReader(new FileInputStream(privateKeyName)));
Object obj;
PrivateKey key = null;
X509Certificate cert1 = null;
X509Certificate cert2 = null;
obj = pemReader.readObject();
if (obj instanceof PrivateKey) {
key = (PrivateKey) obj;
System.out.println("Private Key found");
}
obj = pemReader.readObject();
if(obj instanceof X509Certificate){
cert1 = (X509Certificate) obj;
System.out.println("cert found");
}
obj = pemReader.readObject();
if(obj instanceof X509Certificate){
cert2 = (X509Certificate) obj;
System.out.println("cert found");
}
This prints out:
Private Key Found
cert found
cert found
The type of the keys are:
System.out.println(key.getAlgorithm());
System.out.println(cert1.getSigAlgName());
System.out.println(cert2.getSigAlgName());
RSA
SHA256WithRSAEncryption
SHA256WithRSAEncryption
If I try to decrypt like this:
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, key);
Path path = Paths.get("fileToDecrypt.p7m");
byte[] data = Files.readAllBytes(path);
byte[] decryptedData = cipher.doFinal(data);
I get:
javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes
I have this two files:
fileToDecrypt.p7m
privateKey.pem: containing RSA private key and two X508 Certificates
And I don't know where to begin what to decrypt with what, and how?
Solution to the problem:
private static byte[] cmsDecrypt(byte[] message, PrivateKey key) throws
Exception {
CMSEnvelopedDataParser ep = new CMSEnvelopedDataParser(message);
RecipientInformationStore recipients = ep.getRecipientInfos();
Collection c = recipients.getRecipients();
Iterator iter = c.iterator();
RecipientInformation recipient = (RecipientInformation) iter.next();
return recipient.getContent(key, new BouncyCastleProvider());
}
Path path = Paths.get("fileToDecrypt.p7m");
byte[] data = Files.readAllBytes(path);
try {
System.out.println(new String(cmsDecrypt(data, key)));
} catch (Exception e) {
e.printStackTrace();
}
Related
Generate some keys with OpenSSL, then encode them in Base64 and obtain them and try to generate them to validate the authentication with JWT. Here is the code and description of what happens to me
Generate with the following commands:
openssl req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_key.der
openssl pkcs12 -export -out keyStore.p12 -inkey private_key.pem -in public_key.der
base64 –w 0 private_key.pem > private_key_base64_enc.txt
base64 –w 0 public_key.der > public_key_base64_enc.txt
I saved in my vault.keystore from wildfly: private_key_base64_enc.txt and public_key_base64_enc.txt
Then in my java class I write the following:
private void jwtSignedAuthentication(String token, PropName vaultBlockName) throws Exception
{
String rsa512Alias = vaultBlockName.getDefaultValue();
String rsa512pvt = VaultReader.getValue(rsa512Alias, "privateKey");
String rsa512pbc = VaultReader.getValue(rsa512Alias, "publicKey");
KeyFactory keyfatc = null;
PrivateKey privateKey = null;
PublicKey publicKey = null;
try {
keyfatc = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
logger.error(e);
}
StringBuilder pkcs8Lines = new StringBuilder();
BufferedReader rdr = new BufferedReader(new StringReader(new String(Base64.getDecoder().decode(rsa512pvt.getBytes()))));
String line;
while ((line = rdr.readLine()) != null) {
pkcs8Lines.append(line);
}
// Remove the "BEGIN" and "END" lines, as well as any whitespace
String pkcs8Pem = pkcs8Lines.toString();
pkcs8Pem = pkcs8Pem.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
pkcs8Pem = pkcs8Pem.replace("-----END ENCRYPTED PRIVATE KEY-----", "");
pkcs8Pem = pkcs8Pem.replaceAll("\\s+","");
byte[] dataPvt = Base64.getDecoder().decode(pkcs8Pem.getBytes());
PKCS8EncodedKeySpec specPvt = new PKCS8EncodedKeySpec(dataPvt);
byte[] dataPbc = Base64.getDecoder().decode(rsa512pbc.getBytes());
StringBuilder publicLinesBuilder = new StringBuilder();
BufferedReader readerPlubKey = new BufferedReader(new StringReader(new String(dataPbc)));
String lineP;
while ((lineP = readerPlubKey.readLine()) != null) {
publicLinesBuilder.append(lineP);
}
String pubK = publicLinesBuilder.toString();
pubK = pubK.replace("-----BEGIN CERTIFICATE-----", "");
pubK = pubK.replace("-----END CERTIFICATE-----", "");
pubK = pubK.replaceAll("\\s+","");
X509EncodedKeySpec specPbc = new X509EncodedKeySpec(Base64.getDecoder().decode(pubK.getBytes()));
try {
privateKey = keyfatc.generatePrivate(specPvt);
publicKey = keyfatc.generatePublic(specPbc);
} catch (InvalidKeySpecException e) {
logger.error(e);
}
Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) publicKey, (RSAPrivateKey) privateKey);
// Creación de un verificador JWT
JWTVerifier verifier = JWT.require(algorithm).withIssuer(JWT_CLAIM_ISSUER).acceptLeeway(2).build();
UserContext userContext = new UserContext();
userContext.setUserName(JWT_CLAIM_ISSUER);
try {
// Decode JWT, verificación del token.
#SuppressWarnings("unused")
DecodedJWT decodeJwt = verifier.verify(token);
} catch (JWTDecodeException e) {
logger.error(e);
}
}
When I try to generate the keys I return null:
privateKey = keyfatc.generatePrivate(specPvt);
publicKey = keyfatc.generatePublic(specPbc);
Anyone have any idea what happens with this. Thanks in advance
For generate my JWT:
public ResteasyWebTarget getClientWebAgent(String host, String blockName) throws KeyStoreException
{
ResteasyClient clientBuilder = new ResteasyClientBuilder().establishConnectionTimeout(10, TimeUnit.SECONDS).socketTimeout(5, TimeUnit.SECONDS).build();
ResteasyWebTarget target = clientBuilder.target(host);
KeyPair keys = null;
try {
keys = keyStore.getKeys();
/*logger.infov(new String(Base64.getEncoder().encode(keys.getPrivate().getEncoded())));
logger.infov("****PUBLIC KEY ******");
logger.infov(new String(keys.getPublic().getEncoded()));*/
} catch (IOException e) {
logger.error(e);
}
Algorithm algorithm = Algorithm.RSA512((RSAPublicKey) keys.getPublic(), (RSAPrivateKey) keys.getPrivate());
Map<String, Object> headerClaims = new HashMap<>();
headerClaims.put("alg", "RS512");
headerClaims.put("typ", "JWT");
JWTCreator.Builder jwtCreator = JWT.create();
jwtCreator.withHeader(headerClaims);
jwtCreator.withIssuer(JWT_CLAIM_ISSUER);
jwtCreator.withIssuedAt(LocalDate.now().toDate());
jwtCreator.withExpiresAt(LocalDate.now().toDateTimeAtCurrentTime().plusSeconds(30).toDate());
String jwtToken = jwtCreator.sign(algorithm);
target.register(new BearerAuthenticator(jwtToken));
target.register(new LanguageHeaderToken(Locale.getDefault()));
return target;
}
Your 'public key' is actually a certificate (specifically an X.509 v1 or v3 certificate, depending on your openssl config), which contains a publickey but is different from a publickey -- and is in PEM format even though you have misleadingly named it .der -- and your privatekey is encrypted.
In addition to the approach of using a PKCS12, as Roberto validly proposes and is usually the simplest because it's only one file to manage and is still encrypted and thus more secure:
Java can handle an X.509 certificate, but you use a CertificateFactory.getInstance("X.509") and give it an InputStream instead of a KeyFactory and an X509EncodedKeySpec. CertificateFactory can handle either PEM or DER, unlike KeyFactory which can handle only DER, so you don't need the de-PEM (strip BEGIN/END/EOL and decode base64) parts.
standard Java cannot handle encrypted PKCS8 keys directly. If you can add a thirdparty library, BouncyCastle's bcpkix can do so; search the dozen or so existing Qs that use PEMParser (not PEMReader, that's the older version) and JceOpenSSLPKCS8DecryptorBuilder. Otherwise, you can add -nodes to your req -newkey -x509 command to generate an unencrypted privatekey file, which after you de-PEM it does work in KeyFactory with PKCS8EncodedKeySpec. (It's still spelled -nodes even though the encryption used without it hasn't been plain aka single DES for decades.) Using an unencrypted privatekey file of course means that any intruder or malware on your system that can read that file can get your privatekey, which in many situations is a risk.
finally, if you really want only the keypair and not a certificate, don't bother with req -newkey -x509. Instead use openssl genpkey to generate the privatekey, or the older but simpler openssl genrsa -nodes followed by (or piped to) openssl pkcs8 -topk8 -nocrypt to convert it to PKCS8-unencrypted format. Then use openssl pkey -pubout or the older openssl rsa -pubout to make a separate file with the publickey. Those commands can write (and read back where applicable) DER format instead of PEM; if you do that, your code doesn't need the de-PEM steps, you can just pass the binary file contents to KeyFactory. The risks for an unencrypted file are the same as above.
Maybe you are generating the keystore without assigning a valid alias, looking at your command you are not using the -name option.
The command should be like this:
openssl pkcs12 -export -out keyStore.p12 -inkey private_key.pem -in public_key.der -name "alias"
A smarter way to use the keys in java is by creating a KeyPair:
KeyPair loadKeyPair() throws Exception {
// Read keystore from resource folder
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL resource = classLoader.getResource("keyStore.p12");
File file = new File(Objects.requireNonNull(resource).toURI());
char[] keyPass = "1234".toCharArray();
String alias = "alias";
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
try (FileInputStream is = new FileInputStream(file)) {
keystore.load(is, keyPass);
}
Key key = keystore.getKey(alias, keyPass);
if (key instanceof PrivateKey) {
// Get certificate of public key
Certificate cert = keystore.getCertificate(alias);
// Get public key
PublicKey publicKey = cert.getPublicKey();
// Return a key pair
return new KeyPair(publicKey, (PrivateKey) key);
}
return null;
}
Then extract RSAPublicKey and RSAPrivateKey keys from the KeyPair:
void loadKeys() throws Exception{
KeyPair keyPair = loadKeyPair();
if (null != keyPair) {
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
}
}
Hope it can be helpful and good luck with your Json Web Tokens! :-p
I am trying to load the key from a .crt file. Below is my code
Path path = Paths.get(filePath);
byte[] bytes;
try {
bytes = Files.readAllBytes(path);
/* Generate public key. */
X509EncodedKeySpec ks = new X509EncodedKeySpec(bytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = kf.generatePublic(ks);
Exception thrown #kf.generatePublic(ks).
Exception: java.security.InvalidKeyException: invalid key format
Below is the .crt file contents
-----BEGIN CERTIFICATE-----
MIIBpzCCAVECBEqbmP4wDQYJKoZIhvcNAQEEBQAwXTELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRF
//trimmed key
-----END CERTIFICATE-----
I could not find what's wrong with my code. This key is used to RSA public encrypt a key (PBKDF2 key) in C code.
//openssl
RSA_public_encrypt(msgLen, msg, encryptedMsg, rsaPubKey, RSA_PKCS1_PADDING)
I am trying to implement it in Java. Am I missing anything? Please guide
UPDATE 1:
Overcame the exception using the below code:
InputStream inStream = null;
try {
inStream = RefundUtil.class.getClassLoader().getResourceAsStream("refund.crt");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
PublicKey pubKey = cert.getPublicKey();
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] cipherText = encryptCipher.doFinal(sessionKey);
inStream.close();
return Base64.getEncoder().encodeToString(cipherText);
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
I have a few methods to encrypt and decrypt files. as far as I know, my encrypt function does its job great, while decrypt usually throws an InvalidKeyException, particularly on the Cipher.getInstance("AES"); bit. I have switch this around from RSA to "RSA/CBC/PKCS5Padding" but nothing has worked so far.
Main function:
static String inFile = "";
static String outFile = "";
static String hexKey="";
static String keyStore;
static String keyName;
public static void main(String[] args) {
if (args.length==5 && args[0].equals("-encRSA") ) {
keyStore = args[1];
keyName = args[2];
inFile = args[3];
outFile = args[4];
encryptRSA();
} else if (args.length==5 && args[0].equals("-decRSA") ) {
keyStore = args[1];
keyName = args[2];
inFile = args[3];
outFile = args[4];
decryptRSA();
} else {
System.out.println("This is a simple program to encrypt and decrypt files");
System.out.println("Usage: ");
System.out.println(" -encRSA <keyStore> <keyName> <inputFile> <outputFile> RSA encrypt");
System.out.println(" -decRSA <keyStore> <keyName> <inputFile> <outputFile> RSA decrypt");
}
Encrypt function
private static void encryptRSA() {
try {
//Get the public key from the keyStore and set up the Cipher object
PublicKey publicKey = getPubKey(keyStore,keyName);
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
//Read the plainText
System.out.println("Loading plaintext file: "+inFile);
RandomAccessFile rawDataFromFile = new RandomAccessFile(inFile, "r");
byte[] plainText = new byte[(int)rawDataFromFile.length()];
rawDataFromFile.read(plainText);
// Generate a symmetric key to encrypt the data and initiate the AES Cipher Object
System.out.println("Generating AES key");
KeyGenerator sKenGen = KeyGenerator.getInstance("AES"); //ECB is fine here
Key aesKey = sKenGen.generateKey();
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
// Encrypt the symmetric AES key with the public RSA key
System.out.println("Encrypting Data");
byte[] encodedKey = rsaCipher.doFinal(aesKey.getEncoded());
// Encrypt the plaintext with the AES key
byte[] cipherText = aesCipher.doFinal(plainText);
//Write the encrypted AES key and Ciphertext to the file.
System.out.println("Writting to file: "+outFile);
FileOutputStream outToFile = new FileOutputStream(outFile);
outToFile.write(encodedKey);
outToFile.write(cipherText);
System.out.println("Closing Files");
rawDataFromFile.close();
outToFile.close();
}
catch (Exception e) {
System.out.println("Doh: "+e);
}
}
Decrypt function (so far):
private static void decryptRSA()
{
FileInputStream cipherfile;
try {
cipherfile = new FileInputStream(inFile);
byte[] ciphertext = new byte[cipherfile.available()];
PrivateKey privatekey = getKeyPair().getPrivate();
/* Create cipher for decryption. */
Cipher decrypt_cipher = Cipher.getInstance("AES");
decrypt_cipher.init(Cipher.DECRYPT_MODE, privatekey);
/* Reconstruct the plaintext message. */
byte[] plaintext = decrypt_cipher.doFinal(ciphertext);
FileOutputStream plainfile = new FileOutputStream(outFile);
plainfile.write(plaintext);
plainfile.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static KeyPair getKeyPair() throws Exception
{
KeyPair keypair = null;
FileInputStream is = new FileInputStream(keyStore);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(is, password.toCharArray());
Key key = keystore.getKey(keyName, password.toCharArray());
if (key instanceof PrivateKey) {
Certificate cert = keystore.getCertificate(keyName);
PublicKey publicKey = cert.getPublicKey();
keypair = new KeyPair(publicKey, (PrivateKey) key);
}
return keypair;
}
You need to reverse the encryption process to code the decryption process. Currently you are encrypting an AES key using RSA and then encrypting the plaintext to ciphertext using AES.
In the decryption process you only try and decrypt the ciphertext using AES. You should first extract the encrypted AES key, decrypt that and then decrypt the (rest of the) ciphertext using AES to retrieve the plaintext.
I'm writing a program that uses RSA for various tasks.
I know how to generate and write the key pair to file, but I cannot load the encrypted (AES-256-CFB) key pair to a KeyPair object.
So the question is: how do I load/decrypt an encrypted PEM key pair as a java.security.KeyPair object using the BouncyCastle library?
Thanks.
Generation/export code:
public void generateKeyPair(int keysize, File publicKeyFile, File privateKeyFile, String passphrase) throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
SecureRandom random = new SecureRandom();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
generator.initialize(keysize, random);
KeyPair pair = generator.generateKeyPair();
Key pubKey = pair.getPublic();
PEMWriter pubWriter = new PEMWriter(new FileWriter(publicKeyFile));
pubWriter.writeObject(pubKey);
pubWriter.close();
PEMWriter privWriter = new PEMWriter(new FileWriter(privateKeyFile));
if (passphrase == null) {
privWriter.writeObject(pair);
} else {
PEMEncryptor penc = (new JcePEMEncryptorBuilder("AES-256-CFB"))
.build(passphrase.toCharArray());
privWriter.writeObject(pair, penc);
}
privWriter.close();
}
I am assuming that you have set BouncyCastle as the security provider, for example with:
Security.addProvider(new BouncyCastleProvider());
The code you provided creates two key files, one for the private key and one for the public key. However, the public key is implicitly contained in the private key, so we only have to read the private key file to reconstruct the key pair.
The main steps then are:
Creating a PEMParser to read from the key file.
Create a JcePEMDecryptorProvider with the passphrase required to decrypt the key.
Create a JcaPEMKeyConverter to convert the decrypted key to a KeyPair.
KeyPair loadEncryptedKeyPair(File privateKeyFile, String passphrase)
throws FileNotFoundException, IOException {
FileReader reader = new FileReader(privateKeyFile);
PEMParser parser = new PEMParser(reader);
Object o = parser.readObject();
if (o == null) {
throw new IllegalArgumentException(
"Failed to read PEM object from file!");
}
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
if (o instanceof PEMKeyPair) {
PEMKeyPair keyPair = (PEMKeyPair)o;
return converter.getKeyPair(keyPair);
}
if (o instanceof PEMEncryptedKeyPair) {
PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair)o;
PEMDecryptorProvider decryptor =
new JcePEMDecryptorProviderBuilder().build(passphrase.toCharArray());
return converter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptor));
}
throw new IllegalArgumentException("Invalid object type: " + o.getClass());
}
Example usage:
File privKeyFile = new File("priv.pem");
String passphrase = "abc";
try {
KeyPair keyPair = loadEncryptedKeyPair(privKeyFile, passphrase);
} catch (IOException ex) {
System.err.println(ex);
}
Reference: BouncyCastle unit test for key parsing (link).
First of all, this is not a duplicate question. I am facing a very strange issue.
Following is what I do.
Case 1:
Generate Key Pair
Encrypt Using Private Key
Decrypt Using Public Key
Everything works fine.
Case 2:
Load Certificate from Mozila Firefox Key Store
Use Certificate A
Encrypt Using Private Key of Certificate A
Decrypt Using Public Keu of Certificate A
Everything works fine.
Case 3:
Load Certificate from Internet Explorer Key Store
Use Certificate A
Encrypt Using Private Key of Certificate A
Decrypt Using Public Keu of Certificate A
At Decrypt time, I get error of BadPadding exception
Following is snippet of each of my codes.
Generating Key Pair
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
Load Mozilla KeyStore
String strCfg = System.getProperty("user.home")+ File.separator + "jdk6-nss-mozilla.cfg";
Provider p1 = new sun.security.pkcs11.SunPKCS11(strCfg);
Security.addProvider(p1);
keyStore = KeyStore.getInstance("PKCS11");
keyStore.load(null, "password".toCharArray());
Content of Config file
name=NSS
slot=2
library=C:/Program Files/Mozilla Firefox/softokn3.dll
nssArgs="configDir='C:/Documents and Settings/pratik.vohera.DIGI-CORP/Application Data/Mozilla/Firefox/Profiles/t48xsipj.default' certPrefix='' keyPrefix='' secmod='secmod.db' flags=readOnly"
Load IE KeyStore
keyStore = KeyStore.getInstance("Windows-MY");
keyStore.load(null, null);
Get Public and Private key from KeyStore
if (keyStore != null) {
Enumeration<String> enumaration = null;
try {
enumaration = keyStore.aliases();
} catch (KeyStoreException e1) {
e1.printStackTrace();
}
ArrayList<String> certiList;
while (enumaration.hasMoreElements()) {
String aliases = enumaration.nextElement();
certiList = new ArrayList<String>();
certiList.add(aliases);
try {
selectedCert = keyStore.getCertificate(aliases);
selectedpublickey = (RSAPublicKey) selectedCert.getPublicKey();
selectedAlias = aliases;
selectedprivateKey = (PrivateKey) keyStore.getKey(selectedAlias, null);}
} catch (KeyStoreException e) {
e.printStackTrace();
}
}
Encryption
private static String publicEncrypt(String text, Key pubKey) throws Exception {
BASE64Encoder bASE64Encoder = new BASE64Encoder();
byte[] plainText = text.getBytes();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String encryptedText = bASE64Encoder.encode(cipher.doFinal(plainText));
return encryptedText;
}
Decryption
private static String privateDecrypt(String text, Key priKey)throws Exception {
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] encryptText = base64Decoder.decodeBuffer(text);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String decryptedString = new String(cipher.doFinal(encryptText));
return decryptedString;
}
Exception Stacktrace
javax.crypto.BadPaddingException: Data must start with zero
at sun.security.rsa.RSAPadding.unpadV15(Unknown Source)
at sun.security.rsa.RSAPadding.unpad(Unknown Source)
at com.sun.crypto.provider.RSACipher.a(DashoA13*..)
at com.sun.crypto.provider.RSACipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at test.testclass.privateDecrypt(testclass.java:198)
at test.testclass.test(testclass.java:137)
at test.testclass.main(testclass.java:120)
I have been working on this for a long time. This is very important. Do let me know if any further information is required.
In the third case, the problem is: You try to encrypt using the private key and decrypt using the public key which is wrong. You should always decrypt using the private key.