How to read PKCS8 file with empty password using BouncyCastle - java

I can generate a PKCS#8 encrypted file with empty password using OpenSSL:
~ $ openssl pkcs8 -topk8 -in ca_private.pem
Enter pass phrase for ca_private.pem:
Enter Encryption Password: <ENTER>
Verifying - Enter Encryption Password: <ENTER>
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBvTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIsWq90VBNFMwCAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCFtKOCdDUeRohccAqQZaDIBIIB
YG+ohLBKQ766BTCXXZ7wyAP1l0grcQPgnzI2XmEj33rBIMogS6l3oAN3Faos2I6n
PcUY+aNLQtDSbvPzF4ozd0oWYBTa60iYGboQQ2RolhRRTzNW6K2tWBWUB35v2rLV
VYu7xJMX+dr/PxzhEgaQ4Nerb7v7/EAn4fLv3zcW9f/tPbljKUAiKc/YYP+GjRjA
GyJThdVpyeK6Jflobc3V8gqL8Gk0MgeHmXuUR1+SthA6ia5havH7D/FMLvXxZtRK
CpWOQ8mJp7g7dbUf+qWTLX+dMPQFPZDEofdkCY2/J4dSkgNnPgp+1oxSVpEAAR9v
gWsRezU2KfFUEMIljYOT+s4ZhkeAGtA8qa8qnr0yv9uz1OkzFtrleNf0WV8wRqI7
azo/7ff9TbecseYlTRgR40nd2l3Z9RLMVhsS09vPffYDw3jt+Zqf3m7iEri6eSug
5bMcZTszaQsVT0HOfCcpQ1Q=
-----END ENCRYPTED PRIVATE KEY-----
When reading this with a PEMParser, it returns an instance of PKCS8EncryptedPrivateKeyInfo (as expected).
Trying to decrypt that with JceOpenSSLPKCS8DecryptorProviderBuilder, however, fails with
org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: password empty
There is a hard coded check for empty password when creating the decryption key. Is there a way around this? OpenSSL surely deals just fine reading the PKCS#8 back...
Sample code:
import com.excelfore.api.lib.util.ApiCrypto;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import java.io.FileReader;
class Scratch {
public static void main(String[] args) throws Exception {
try (FileReader fr = new FileReader(args[0])) {
Object whatIsIt = new PEMParser(fr).readObject();
if (whatIsIt instanceof PKCS8EncryptedPrivateKeyInfo) {
PKCS8EncryptedPrivateKeyInfo pInfo = (PKCS8EncryptedPrivateKeyInfo) whatIsIt;
InputDecryptorProvider provider =
new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(ApiCrypto.bouncyCastleProvider).build(args[1].toCharArray());
pInfo.decryptPrivateKeyInfo(provider);
System.out.println("ok");
} else {
System.out.println("I don't want "+whatIsIt.getClass().getName());
}
}
}
}

Not sure if there is what you expect or not (do not test yet with empty passphrase)
public static KeyPair parseKeyPair(Path pemFile, String passPhrase) {
try(PEMParser pemParser = new PEMParser(Files.newBufferedReader(pemFile))) {
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProviderHolder.BC_PROVIDER);
if(object instanceof PEMEncryptedKeyPair) {
if(passPhrase == null)
throw new IllegalArgumentException("Pass phrase required for parsing RSA private key");
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(passPhrase.toCharArray());
return converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv));
}
if(object instanceof PEMKeyPair) {
return converter.getKeyPair((PEMKeyPair) object);
}
} catch(Exception e) {
throw new PicApplicationException("Couldn't parse pem to keypair", e);
}
throw new PicApplicationException("Couldn't parse pem to keypair");
}
You may pass empty passphrase to this line:
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build("".toCharArray());

The BouncyCastle provider will not use an empty passphrase for PKCS8 decryption (actually any PBE). The SunJCE provider will, BUT you cannot specify it explicitly as the provider to use, because it doesn't handle AlgorithmParameters for the cipher by OID for PBES2; instead you must let the DecryptorProvider use the default JCA search and have Bouncy provider in that search after SunJCE; the simplest way is to put it at the end with Security.addProvider:
Security.addProvider(new BouncyCastleProvider());
try( Reader r = new FileReader(args[0]); PEMParser p = new PEMParser(r) ) {
JceOpenSSLPKCS8DecryptorProviderBuilder b = new JceOpenSSLPKCS8DecryptorProviderBuilder();
//DON'T:if( args.length>1 ) b.setProvider(args[1]);
InputDecryptorProvider d = b.build(new char[0]);
PrivateKeyInfo k0 = ((PKCS8EncryptedPrivateKeyInfo)p.readObject()).decryptPrivateKeyInfo(d);
PrivateKey k1 = new JcaPEMKeyConverter().getPrivateKey(k0);
System.out.println (k1.getAlgorithm());
}

Related

Import elliptic curve Certificate and Private Key into Java Keystore using java.security.KeyStore

I'm currently working on a project that involves getting an Elliptic Curve Certificate / Private Key package in either PEM or DER format from Vault and needing to import it into a Java Keystore. I can find plenty of information on doing this with keytool, but am unable to find information about how to successfully convert a DER encoded string into a PrivateKeyEntry to insert it into the keystore.
Below is my non-working code. certificate , key, and issuingCa are all PEM or DER encoded Strings (I can specify which format I want to get back from the issuer and pass whichever I can get to work)
private KeyStore packKeystore(String certificate, String key, String issuingCa, String name) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
// Create the keystore
KeyStore retVal = KeyStore.getInstance(KeyStore.getDefaultType());
retVal.load(null, sslKeystorePassword.toCharArray());
var cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
var cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certificate.getBytes()));
retVal.setCertificateEntry(name, cert);
Certificate issuer = null;
if (issuingCa != null) {
issuer = cf.generateCertificate(new ByteArrayInputStream(issuingCa.getBytes(StandardCharsets.UTF_8)));
}
if (key != null) {
var certs = new HashSet<Certificate>();
certs.add(issuer);
certs.add(cert);
PrivateKeyEntry pk = /// How do I create this from what I have????
retVal.setKeyEntry( pk, certs.toArray());
}
return retVal;
}
After some experimentation and research I've learned that the PrivateKey class doesn't like the "old" PEM format where the private key looks like ---- BEGIN EC PRIVATE KEY-----.
I was eventually able to parse a PrivateKey object from a keypair like this:
var parsedKey = new org.bouncycastle.openssl.PEMParser(new StringReader(key)).readObject();
var pair = new org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter().getKeyPair((org.bouncycastle.openssl.PEMKeyPair) parsedKey);
retVal.setKeyEntry(name, pair.getPrivate(), "".toCharArray(), certArray);
From there I get an error on setKeyEntry about the certificate algorithm not matching the private key algorithm. Upon inspection it seems that the Certificate object says the algo is EC and the PK object says the algo is ECDSA.
I eventually solved it this way. Note the key must be in base64 encoded DER format:
private PrivateKey convertECPrivateKeyString(String key) {
...
byte[] keyBytes = null;
if (key != null) {
keyBytes = Base64.getDecoder().decode(key);
}
try (var asnStream = new ASN1InputStream(keyBytes)) {
var primitive = asnStream.readObject();
asnStream.close();
if (primitive instanceof ASN1Sequence) {
var sequence = (ASN1Sequence) primitive;
var pKey = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(sequence);
var pkInfo = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters()), pKey);
return new JcaPEMKeyConverter().getPrivateKey(pkInfo);
}
}
...
}

Verify Digital Signature on Android

I am developing an Android application that requires Digitally signing an html document.
The document resides in the DB, in a JSON form.
I'm signing the document locally using a BASH Script I found on some other SO question :
openssl dgst -sha1 someHTMLDoc.html > hash
openssl rsautl -sign -inkey privateKey.pem -keyform PEM -in hash > signature.bin
Private key was generated using :
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:3 -out privateKey.pem
Public key was generated using :
openssl pkey -in privateKey.pem -out publicKey.pem -pubout
I want to verify the signature created in Signature.bin together with the data in someHTMLDoc.html, back in the application.
I am sending both the html and signature as JSON Object ex:
{ "data" : "<html><body></body></html>", "signature":"6598 13a9 b12b 21a9 ..... " }
The android application holds the PublicKey in shared prefs as follows :
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0AAAEFAAOCAQ0AvniCAKCAQEAvni/NSEX3Rhx91HkJl85
\nx1noyYET ......
Notice the "\n" (newline) in there (was automatically added when copying string from publicKey.pem to Android Gradle Config.
Ok, after all preparations, now the question.
I am trying to validate the key with no success.
I am using the following code :
private boolean verifySignature(String data, String signature) {
InputStream is = null;
try {
is = new ByteArrayInputStream(Config.getDogbarPublic().getBytes("UTF-8")); //Read DogBar Public key
BufferedReader br = new BufferedReader(new InputStreamReader(is));
List<String> lines = new ArrayList<String>();
String line;
while ((line = br.readLine()) != null)
lines.add(line);
// removes the first and last lines of the file (comments)
if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size() - 1).startsWith("-----")) {
lines.remove(0);
lines.remove(lines.size() - 1);
}
// concats the remaining lines to a single String
StringBuilder sb = new StringBuilder();
for (String aLine : lines)
sb.append(aLine);
String key = sb.toString();
byte[] keyBytes = Base64.decode(key.getBytes("utf-8"), Base64.DEFAULT);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(spec);
Signature signCheck = Signature.getInstance("SHA1withRSA"); //Instantiate signature checker object.
signCheck.initVerify(publicKey);
signCheck.update(data.getBytes());
return signCheck.verify(signature.getBytes()); //verify signature with public key
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
Can anyone help ? what am i doing wrong ?
Am i missing some byte conversion ? maybe the JSON object is affecting the signature ?
Should a signature contain the \n (linebreak) that the original file contains or should it be without in the JSON file ?
Thanks in advance for all the help, its highly appreciated.
Digital signature is a process of computing digest (function H) of data (C) and encrypting it with asymmetric encryption algorithm (function E) to produce cypher text (S):
S = E(H(C))
Signature verification takes the signature decrypts the given signature (function D) - which results in H(C) only if the public key used in decryption is paired with private key used in encryption, and computes the digest of data to check if the two digests match:
H(C) == D(E(H(C)))
It's clear from this that the bytes given to the hash function (C) must be exactly the same in order for the signature to validate.
In your case they are not, because when you're computing the digest using openssl dgst the output (H(C) on the right) is literally something like:
SHA1(someHTMLDoc.html)= 22596363b3de40b06f981fb85d82312e8c0ed511
And this is the input to the RSA encryption.
And when you're verifying the signature, the output of the digest (H(C) on the left) are the raw bytes, for instance in hex:
22596363b3de40b06f981fb85d82312e8c0ed511
So you end up encrypting bytes to produce (H(C) on the right):
0000000: 5348 4131 2873 6f6d 6548 746d 6c44 6f63 SHA1(someHtmlDoc
0000010: 2e68 746d 6c29 3d20 3232 3539 3633 3633 .html)= 22596363
0000020: 6233 6465 3430 6230 3666 3938 3166 6238 b3de40b06f981fb8
0000030: 3564 3832 3331 3265 3863 3065 6435 3131 5d82312e8c0ed511
0000040: 0a .
and comparing against bytes (H(C) on the left):
0000000: 2259 6363 b3de 40b0 6f98 1fb8 5d82 312e "Ycc..#.o...].1.
0000010: 8c0e d511 ....
Also you need to use -sign with openssl dgst in order to have proper output format (see Difference between openSSL rsautl and dgst).
So on the OpenSSL side do:
openssl dgst -sha1 -sign privateKey.pem someHTMLDoc.html > signature.bin
On the Java side do:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import org.spongycastle.util.io.pem.PemObject;
import org.spongycastle.util.io.pem.PemReader;
public class VerifySignature {
public static void main(final String[] args) throws Exception {
try (PemReader reader = publicKeyReader(); InputStream data = data(); InputStream signatureData = signature()) {
final PemObject publicKeyPem = reader.readPemObject();
final byte[] publicKeyBytes = publicKeyPem.getContent();
final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
final X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
final RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(publicKeySpec);
final Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(publicKey);
final byte[] buffy = new byte[16 * 1024];
int read = -1;
while ((read = data.read(buffy)) != -1) {
signature.update(buffy, 0, read);
}
final byte[] signatureBytes = new byte[publicKey.getModulus().bitLength() / 8];
signatureData.read(signatureBytes);
System.out.println(signature.verify(signatureBytes));
}
}
private static InputStream data() throws FileNotFoundException {
return new FileInputStream("someHTMLDoc.html");
}
private static PemReader publicKeyReader() throws FileNotFoundException {
return new PemReader(new InputStreamReader(new FileInputStream("publicKey.pem")));
}
private static InputStream signature() throws FileNotFoundException {
return new FileInputStream("signature.bin");
}
}
I've used Spongy Castle for PEM decoding of the public key to make things a bit more readable and easier to use.
If you have a digitally signed XML file (downloaded from the web) and a certificate (.cer file) and you want to verify the digital signature in an android app then here is the code:
You need two things xmlFilePath and certificateFilePath
boolean verifySignature() {
boolean valid = false;
try {
File file = new File("xmlFilePath");
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setNamespaceAware(true);
Document doc = f.newDocumentBuilder().parse(file);
NodeList nodes = doc.getElementsByTagNameNS(Constants.SignatureSpecNS, "Signature");
if (nodes.getLength() == 0) {
throw new Exception("Signature NOT found!");
}
Element sigElement = (Element) nodes.item(0);
XMLSignature signature = new XMLSignature(sigElement, "");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream ims = new InputStream("certificateFilePath");
X509Certificate cert = (X509Certificate) cf.generateCertificate(ims);
if (cert == null) {
PublicKey pk = signature.getKeyInfo().getPublicKey();
if (pk == null) {
throw new Exception("Did not find Certificate or Public Key");
}
valid = signature.checkSignatureValue(pk);
} else {
valid = signature.checkSignatureValue(cert);
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Failed signature " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
return valid;
}
If you want to do it in java but not in android studio. Here is the code:
public static boolean isXmlDigitalSignatureValid(String signedXmlFilePath,
String pubicKeyFilePath) throws Exception {
boolean validFlag;
File file = new File(signedXmlFilePath);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(file);
doc.getDocumentElement().normalize();
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0) {
throw new Exception("No XML Digital Signature Found, document is discarded");
}
FileInputStream fileInputStream = new FileInputStream(pubicKeyFilePath);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(fileInputStream);
PublicKey publicKey = cert.getPublicKey();
DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(0));
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
validFlag = signature.validate(valContext);
return validFlag;
}
The reason is that you will need to add dependency if you use the same code in android studio, sometimes confusing also.
If you are interested in reading digital signature documents, you can read www.xml.com/post It is an interesting document for understanding the need for a digital signature.

RSA Encryption Javascript and Decrypt Java

Spent almost 2 days with different combinations.I am generating a asymmetric key pair (public and private) in java using RSA algorithm and trying to use the public key in javascript to encrypt some text and decrypt back in java on server side. I am getting "javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes" exception while trying to decrypt back the string encrypted in javascript. Would appreciate some help...
Using thi Javascript library to encrypt.
https://github.com/wwwtyro/cryptico
var publicKeyString = ""// base64encoded public key string generated in java
Here is my javascript code
var EncryptionResult = cryptico.encrypt("somestring", publicKeyString);
console.log("Encrypted status-"+EncryptionResult.status);
console.log("Encrypted String-"+EncryptionResult.cipher);
It is successfully encrypting the string.
Java Key Generation and Decryption
Cipher cipher = Cipher.getInstance("RSA");
KeyFactory fact = KeyFactory.getInstance("RSA");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024); // 1024 used for normal
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
FileOutputStream fos = null;
ObjectOutputStream oos = null;
Code to store the private key in file which is used to decrypt in decrypt method.
RSAPrivateKeySpec rsaPrivKeySpec = fact.getKeySpec(privateKey,
RSAPrivateKeySpec.class);
System.out.println("Writing private key...");
fos = new FileOutputStream(PRIVATE_KEY_FILE);
oos = new ObjectOutputStream(new BufferedOutputStream(fos));
oos = new ObjectOutputStream(new BufferedOutputStream(fos));
oos.writeObject(rsaPrivKeySpec.getModulus());
oos.writeObject(rsaPrivKeySpec.getPrivateExponent());
oos.close();
Decrypt method
public String decrypt(String ciphertext)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException
{
if (ciphertext.length() == 0) return null;
byte[] dec = org.apache.commons.codec.binary.Base64.decodeBase64(ciphertext);
try {
System.out.println("Private Key file name----"+PRIVATE_KEY_FILE);
privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE);
} catch (IOException e) {
e.printStackTrace();
}
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(dec);
return new String(decrypted, PLAIN_TEXT_ENCODING);
}
//reading private key from file
public PrivateKey readPrivateKeyFromFile(String fileName)
throws IOException {
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(new File(fileName));
ois = new ObjectInputStream(fis);
System.out.println("Private Key file-"+fileName);
BigInteger modulus = (BigInteger) ois.readObject();
BigInteger exponent = (BigInteger) ois.readObject();
// Get Private Key
RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PrivateKey privateKey = fact.generatePrivate(rsaPrivateKeySpec);
return privateKey;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ois != null) {
ois.close();
if (fis != null) {
fis.close();
}
}
}
return null;
}
From the Cryptico documentation it seems that it is not a simple RSA encryption, but a complex operation that generates AES key, encrypts it with RSA, encrypts the data with AES and outputs a concatenation of encrypted AES key and encrypted data. If you want to decrypt that in Java you will have to check the Cryptico source code and reimplement the same in Java.
As for your current attempt and javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes error:
When you do not specify the full transformation the default JCE transformation for RSA is RSA/ECB/PKCS1Padding.
In this mode the RSA encrypts or decrypts a single block of data which the length is not greater than the size of the key (more specifically, if the input sequence of bytes is interpreted as a big integer, its value should be less that the modulus used by the RSA). You can find additional information in this and this questions.
With the key size of 1024 bits the maximum data size is 128 bytes, and that is exactly what the exception says because the output of Cryptico is obviously not a single RSA block and its length is greater that expected by "plain" RSA. Trying to use some other cipher mode or padding mode in Java will not help in that situation either.
Thanks Oleg for the detailed information. I will definitely take a look into it.
For now I switched to jsencrypt and it seems to work fine.
https://github.com/travist/jsencrypt
EDIT
How you get the encoded public key for the js encrypt?
Here is the solution for data Encryption from JS and Decrypt on Java(server side). I have used Cryptico js library for encryption(http://wwwtyro.github.io/cryptico/).
First of all we have to generate the java Keystore file from your local system. Don't use other Keystore files such as online Keystore. For creating the java Keystore(JKS) you can use KeyStore Explorer tool.
Below is the config I have used, using KeyStore Explorer tool
Keystore type - JKS
RSA algorithm - Keysize 1024
Version - version 3
Signature algorithm - SHA256 with RSA
Validity period - 99 years(based on your requirement)
Name filed - Fill all the mandatory fields - remember the "alias" and "password" what you entered here.
Finally, save the file as .jks on your local system.
Step-1
we have to use this Keystore file on the java side and we send the public key to the frontend.
I have created the service class which is responsible to load Keystore from the keystore file path(string), Keypair and Decrypt. You have to provide the alias, password, keystore type.
public KeyPair getExistingKeyStoreKeyPair(String keystorePath){
KeyPair generateKeyPair = null
try {
File file = new File(keystorePath)
KeyStore keyStore = loadKeyStore(file, "password", "JKS")
generateKeyPair = getKeyPair(keyStore, "fin360", "password")
} catch (Exception ex){
println(ex)
}
return generateKeyPair
}
public KeyStore loadKeyStore(final File keystoreFile, final String password, final String keyStoreType) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
if (null == keystoreFile) {
throw new IllegalArgumentException("Keystore url may not be null")
}
final URI keystoreUri = keystoreFile.toURI()
final URL keystoreUrl = keystoreUri.toURL()
final KeyStore keystore = KeyStore.getInstance(keyStoreType)
InputStream is = null
try {
is = keystoreUrl.openStream();
keystore.load(is, null == password ? null : password.toCharArray())
} finally {
if (null != is) {
is.close()
}
}
return keystore;
}
public KeyPair getKeyPair(final KeyStore keystore, final String alias, final String password) {
PublicKey publicKey
PrivateKey privateKey
Key key
KeyPair keyPair
try {
key = (PrivateKey) keystore.getKey(alias, password.toCharArray())
final Certificate cert = keystore.getCertificate(alias)
publicKey = cert.getPublicKey()
privateKey = key
keyPair = new KeyPair(publicKey, privateKey)
} catch (Exception ex){
println(ex)
}
return keyPair;
}
public decryptData(String data, String keystorePath) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException{
try {
byte[] dectyptedText = new byte[1]
byte[] byteArray = new byte[256]
BigInteger passwordInt = new BigInteger(data, 16)
if (passwordInt.toByteArray().length > 256) {
for (int i=1; i<257; i++) {
byteArray[i-1] = passwordInt.toByteArray()[i]
}
} else {
byteArray = passwordInt.toByteArray();
}
KeyPair generateKeyPair = getExistingKeyStoreKeyPair(keystorePath)
PrivateKey privateKey = generateKeyPair.getPrivate()
Cipher cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.DECRYPT_MODE, privateKey)
dectyptedText = cipher.doFinal(byteArray)
String txt2 = new String(dectyptedText)
return txt2
}
catch (Exception ex){
println(ex)
return null
}
}
decryptData() method will playing the main role here. When you send the value data.getBytes() directly to the dycrypt method cipher.doFinal(byteArray) you get the exception - IllegalBlockSizeException size should not more than 128 bytes. So we have get rid of the issue I get the workaroud here - [Getting 1 byte extra in the modulus RSA Key and sometimes for exponents also
Basically it adds the zero when we converting data from BigInteger to byteArray. So I removed the zero from the array.
Let's start use the service class to get the key values.
String publicKey= null
String keystorePath = your file path
KeyPair generateKeyPair = encryptDecryptService.getExistingKeyStoreKeyPair(keystorePath)
PublicKey publicKey1 = generateKeyPair.getPublic()
KeyFactory keyFactory;
RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(BigInteger.ZERO, BigInteger.ZERO)
try {
keyFactory = KeyFactory.getInstance("RSA")
rsaPublicKeySpec = keyFactory.getKeySpec(publicKey1, RSAPublicKeySpec.class)
} catch(NoSuchAlgorithmException e1) {
println(e1)
} catch(InvalidKeySpecException e) {
println(e)
}
String testPublicKey = rsaPublicKeySpec.getModulus().toString(16)
publicKey = testPublicKey
Send you publicKey to JS.
In your HTML or servlet import all the required js and jar files(you will get it from the cryptico js library).
try{
var rsa = new RSAKey();
rsa.setPublic(pub, "10001");
password = rsa.encrypt(password);
formdata = "password="+password+"&dataEncrypt=true";
}
catch (error){
console.log(error);
}
above I have directly used new RSA() instance(in cryptico library it will be different. Internaly library is using the same) and set the publickey to the instance. We have to use hex string value is '10001'. Form the query string with encrypted data which we send to server. Form data holds the encrypted data well as well as 'dataEncrypt' key value. I used to check whether data is encrypted or not.
Finally on the server side you will get the request params and below is the code for decrypt.
Boolean isDataEncrypted = false
String decryptedPassword = null
isDataEncrypted = params.containsKey("dataEncrypt")
if(params.containsKey("password")){
if(isDataEncrypted) {
String keystorePath = helperService.fetchKeystoreFilePath()
decryptedPassword = encryptDecryptService.decryptData(params.password, keystorePath)
// update decrypted data into request params
params.password = decryptedPassword
}
}
println("Data decrypted => " + decryptedPassword)

Decrypt Content Encryption Key in JSON Web Encryption

I am using OpenSSL RSA1_5 for decrypting the CEK (Content Encryption Key).
My aim is to decrypt the JWK (JSON Web Key) by which I will be getting CEK, so by using CEK I can decrypt my ciphertext which is actually the encrypted data.
After using Base64Decode, JWE Header is
{"alg":"RSA1_5","enc":"A128CBC-HS256","typ":"JOSE"}
where "alg" is the algorithm used to decrypt the CEK. Please help me decrypting the CEK first, after this I need to decrypt the Cipher.
My Java class is:
package com.decryption;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
public class RSADecrypt
{
public RSADecrypt(String inFileName, String outFileName) {
try {
System.out.println("Inside TRY");
/* Get the encrypted message from file. */
FileInputStream cipherfile = new FileInputStream(inFileName);
byte[] ciphertext = new byte[cipherfile.available()];
cipherfile.read(ciphertext);
cipherfile.close();
System.out.println("Inside 1");
/* Get the private key from file. */
//PrivateKey privatekey = readPrivateKey("D://sso//mmdevnopass.key");
PrivateKey privatekey = readPrivateKey("D://sso//mmdevJWE.key");
System.out.println("Inside 2");
/* Create cipher for decryption. */
Cipher decrypt_cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
decrypt_cipher.init(Cipher.DECRYPT_MODE, privatekey);
System.out.println("Inside 3");
/* Reconstruct the plaintext message. */
byte[] plaintext = decrypt_cipher.doFinal(ciphertext);
FileOutputStream plainfile = new FileOutputStream(outFileName);
plainfile.write(plaintext);
plainfile.close();
} catch (Exception e) {
System.out.println("catch1");
e.printStackTrace();
}
}
public static PrivateKey readPrivateKey(String filename) throws Exception {
System.out.println("readPrivateKey()");
FileInputStream file = new FileInputStream(filename);
byte[] bytes = new byte[file.available()];
file.read(bytes);
file.close();
System.out.println("readPrivateKey() 1");
PKCS8EncodedKeySpec privspec = new PKCS8EncodedKeySpec(bytes);
// X509EncodedKeySpec privspec= new X509EncodedKeySpec(bytes);
//RSAPrivateKeySpec privspec = new RSAPrivateKeySpec(modulus, privateExponent)
System.out.println("readPrivateKey() 2");
KeyFactory factory = KeyFactory.getInstance("RSA");
System.out.println("readPrivateKey() 3");
PrivateKey privkey = factory.generatePrivate(privspec);
System.out.println("readPrivateKey() 4");
return privkey;
}
public static void main(String[] arg) {
/*if (arg.length != 2) {
System.err.println("Usage: java RSADecrypt <src file> <dest file>");
} else {*/
System.out.println("Welcome");
String inFileName="D://sso//myJEK.txt";
String outFileName="D://sso//out.txt";
new RSADecrypt(inFileName,outFileName);
// }
}
}
I am getting output as
Welcome
Inside TRY
Inside 1
readPrivateKey()
readPrivateKey() 1
readPrivateKey() 2
readPrivateKey() 3
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:175)
at java.security.KeyFactory.generatePrivate(KeyFactory.java:322)
at com.decryption.RSADecrypt.readPrivateKey(RSADecrypt.java:85)
at com.decryption.RSADecrypt.<init>(RSADecrypt.java:46)
at com.decryption.RSADecrypt.main(RSADecrypt.java:102)
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:324)
at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:350)
at sun.security.rsa.RSAPrivateCrtKeyImpl.<init>(RSAPrivateCrtKeyImpl.java:74)
at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(RSAPrivateCrtKeyImpl.java:58)
at sun.security.rsa.RSAKeyFactory.generatePrivate(RSAKeyFactory.java:274)
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:171)
... 4 more
catch1
Please help me to decrypt the CEK and solve this exception.
Your problem is caused by your private key file. Firstly, your method of reading the bytes is error-prone:
FileInputStream file = new FileInputStream(filename);
byte[] bytes = new byte[file.available()];
file.read(bytes);
file.close();
This may not read the entire file. The available() method does not indicate how many bytes are in the file. Please search for a better way of reading this file (perhaps from this question: File to byte[] in Java).
Once this is fixed, you may still have errors unless your file is a DER-encoded PKCS #8 object. A common mistake is to try and use a PEM-encoded file (e.g. containing ----- BEGIN PRIVATE KEY ---- headers and base64-encoded data).

How to Load RSA Private Key From File

I am working on a test harness for a SAML 1.1 Assertion Consumer Service. The test must generate a signed SAMLResponse and submit it to the ACS encoded in Base64. The ACS must be able to verify the signed message using the X509 public cert.
I am able to build the SAMLResponse, adding the necessary assertions, etc. But when I try to sign the object I am running into problems. Here is a snippet of my current code:
String certPath = "mycert.pem";
File pubCertFile = new File(certPath);
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(new FileInputStream(pubCertFile));
} catch(FileNotFoundException e) {
throw new Exception("Could not locate certfile at '" + certPath + "'", e);
}
CertificateFactory certFact = null;
Certificate cert = null;
try {
certFact = CertificateFactory.getInstance("X.509");
cert = certFact.generateCertificate(bis);
} catch(CertificateException e) {
throw new Exception("Could not instantiate cert", e);
}
bis.close();
ArrayList<Certificate> certs = new ArrayList<Certificate>();
certs.add(cert);
String keyPath = "mykey.pem";
File privKeyFile = new File(keyPath);
try {
bis = new BufferedInputStream(new FileInputStream(privKeyFile));
} catch(FileNotFoundException e) {
throw new Exception("Could not locate keyfile at '" + keyPath + "'", e);
}
byte[] privKeyBytes = new byte[(int)privKeyFile.length()];
bis.read(privKeyBytes);
bis.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
KeySpec ks = new PKCS8EncodedKeySpec(privKeyBytes);
RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(ks);
samlResponse.sign(Signature.getInstance("SHA1withRSA").toString(), privKey, certs);
The error occurs on the second-to-last line. I see the following in the console:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
Though not customary or secure, but for sake of this thread, I am providing the public cert and private key that I am using. I of course will re-create new ones once the problem is solved. :)
aj#mmdev0:~/$ cat mykey.pem
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDnbcLSlDFaDMhalcmQgclTFobpkHQHJtxMVGRlbv7zknttAVbY
1jzGjJ6HVupndzDxA9tbiMjQujmGlS/8g5IEbVsR9o6dmcmbvujtEZ2rHZ82tMYP
VAt2IoS/W/q2Rr1cAZ/zTKEmh0ZZjzCZFueLfrYPm3am5JLcXgVtbKwybQIDAQAB
AoGBAJ441oettYgBUUFNQv8/HGtn7Vjl38277cVptTH8DuZr8WJ3Fe8tmWONZBzX
eW6/eIBuyJvuCo1ZpFa0zJfxQ/Ph6QlQwdN50GNfh9RzSS6lDdfy8BRhc27sypXS
L6c5ljB6ql+pp3DdxFhJMOs3ZmBJdeyWe7uFrkngtnM1nxZBAkEA+1hbV1Q305wa
u8YMF1SlNIAfgLJ7buD43SEXle0egz405PFG8f8yDmvROwDiRceILGVrRbInd7Cb
dvJKr34WOQJBAOu2+reG44rNuiXeGX1MYg6TlWYyABm7PrTrhPZkedodOQB8p7zD
AqtDSK7RnDCoThndPW6kdNAeB+kG4ug5XdUCQHRDU8UajNRSkj8nhjJIkj6twWS7
qsMIR7Wp+An+7C1TWg5I2UNZg2MOVnNPnlseyAuZQjy0AvOnetJTk16IGWkCQQCL
FUbOr8rnhgiGe4yywDVDwJVw3aPtiuyvOCEWeabkqkWOIf+fg7m5cFQcwxXUKBsd
a8vp0yQSAQZN24Bb4i2ZAkEA8xGJFlFDY9HREWZnDey5STgbUeT1wYkyKcDsUrp1
kR/3BliGqSIfje+mSKDIZqaP+gai/8bIABYAsDP/t6+cuA==
-----END RSA PRIVATE KEY-----
aj#mmdev0:~/$ cat mycert.pem
-----BEGIN CERTIFICATE-----
MIID7zCCA1igAwIBAgIJAKrURaAaD6ulMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xHDAa
BgNVBAoTE0hvc3R3YXkgQ29ycG9yYXRpb24xITAfBgNVBAsTGFJlc2VhcmNoIGFu
ZCBEZXZlbG9wbWVudDEYMBYGA1UEAxMPd3d3Lmhvc3R3YXkuY29tMR0wGwYJKoZI
hvcNAQkBFg5hakBob3N0d2F5LmNvbTAeFw0xMDA3MTQwMjMyMDhaFw0xMTA3MTQw
MjMyMDhaMIGsMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
BAcTB0NoaWNhZ28xHDAaBgNVBAoTE0hvc3R3YXkgQ29ycG9yYXRpb24xITAfBgNV
BAsTGFJlc2VhcmNoIGFuZCBEZXZlbG9wbWVudDEYMBYGA1UEAxMPd3d3Lmhvc3R3
YXkuY29tMR0wGwYJKoZIhvcNAQkBFg5hakBob3N0d2F5LmNvbTCBnzANBgkqhkiG
9w0BAQEFAAOBjQAwgYkCgYEA523C0pQxWgzIWpXJkIHJUxaG6ZB0BybcTFRkZW7+
85J7bQFW2NY8xoyeh1bqZ3cw8QPbW4jI0Lo5hpUv/IOSBG1bEfaOnZnJm77o7RGd
qx2fNrTGD1QLdiKEv1v6tka9XAGf80yhJodGWY8wmRbni362D5t2puSS3F4FbWys
Mm0CAwEAAaOCARUwggERMB0GA1UdDgQWBBQI/4Inzs6OH5IquItuKhIrhPb24zCB
4QYDVR0jBIHZMIHWgBQI/4Inzs6OH5IquItuKhIrhPb246GBsqSBrzCBrDELMAkG
A1UEBhMCVVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdDaGljYWdvMRww
GgYDVQQKExNIb3N0d2F5IENvcnBvcmF0aW9uMSEwHwYDVQQLExhSZXNlYXJjaCBh
bmQgRGV2ZWxvcG1lbnQxGDAWBgNVBAMTD3d3dy5ob3N0d2F5LmNvbTEdMBsGCSqG
SIb3DQEJARYOYWpAaG9zdHdheS5jb22CCQCq1EWgGg+rpTAMBgNVHRMEBTADAQH/
MA0GCSqGSIb3DQEBBQUAA4GBAA388zZp6UNryC/6o44hj7wTBQdzFFM5cs3B668A
ylAnnal+J8RMIeCHoMF4S7yFQtYdOiWeScgw3c7KXrhJK1X7fU3I+eb1t3Yp1cTI
htyzw14AoiICFalmlVgTCsn3+uh6AXP02PTkR8osdEpUOlWap4uzSKYNKc7tLOFd
4CkM
-----END CERTIFICATE-----
Thanks!
You need to convert your private key to PKCS8 format using following command:
openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key_file -nocrypt > pkcs8_key
After this your java program can read it.
Two things. First, you must base64 decode the mykey.pem file yourself. Second, the openssl private key format is specified in PKCS#1 as the RSAPrivateKey ASN.1 structure. It is not compatible with java's PKCS8EncodedKeySpec, which is based on the SubjectPublicKeyInfo ASN.1 structure. If you are willing to use the bouncycastle library you can use a few classes in the bouncycastle provider and bouncycastle PKIX libraries to make quick work of this.
import java.io.BufferedReader;
import java.io.FileReader;
import java.security.KeyPair;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
// ...
String keyPath = "mykey.pem";
BufferedReader br = new BufferedReader(new FileReader(keyPath));
Security.addProvider(new BouncyCastleProvider());
PEMParser pp = new PEMParser(br);
PEMKeyPair pemKeyPair = (PEMKeyPair) pp.readObject();
KeyPair kp = new JcaPEMKeyConverter().getKeyPair(pemKeyPair);
pp.close();
samlResponse.sign(Signature.getInstance("SHA1withRSA").toString(), kp.getPrivate(), certs);

Categories