What can i use equivalent to RSAPKCS1SignatureDeformatter in java? - java

I have file creation process in which we create a signature using RSAPKCS1Signature class for signing & validating in c#.
Now we are moving to Java, we are using the same algorithm used in c# but it's not validating the same file in java which is created in c#.
I have attached the sample code using. Please suggest need full.
Thanks..!
C#:
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
byte[] Hash = {59,4,248,102,77,97,142,201,210,12,224,93,25,41,100,197,213,134,130,135};
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(RSA);
RSAFormatter.SetHashAlgorithm("SHA1");
byte[] SignedHash = RSAFormatter.CreateSignature(Hash);
RSAPKCS1SignatureDeformatter RSADeformatter = new
RSAPKCS1SignatureDeformatter(RSA);
RSADeformatter.SetHashAlgorithm("SHA1");
Console.WriteLine(RSADeformatter.VerifySignature(Hash, SignedHash));
Java:
KeyStore ex = KeyStore.getInstance("JKS");
ex.load("c://sample.jks", "password");
PrivateKey privateKey = (PrivateKey) ex.getKey("1", "password");
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initSign(privateKey);
String line = null;
while ((line = reader.readLine()) != null) {
signature.update(line.getBytes());
}
signature.sign();

Related

Create CMS Enveloped Data with already encrypted content and encrypted key

tldr:
Is there a way to create CMS Enveloped Data when i have already encrypted content with AES and secret key which is already encrypted with public key?
Long version:
I have an application which encrypts and decrypts data with AES (CBC and GCM mode). Symetric key is encrypted/decrypted with RSA key pairs. When user requests for data we decrypt it in backend (Java) and send it to the browser
Usually we have public key and private key but there is requirement that in some cases we dont have private key and the decryption should take place in browser (user provides PFX with privatkey). The solution for this is PKI.js which can decrypt data using PFX and CMS Enveloped Data.
The problem is that we already encrypted the data and dont have access to plain data which we can use to build CMS Enveloped Data.
Edit:
#dave_thompson_085 thank you for reply! I have an follow-up question. I dont hold certificates in system so only thing i have is public key. Is there a way to adjust your code to this requirement?
Before your answer i was encrypting data for second time just for CMS Enveloped Object. In this code i used only public key for generating reciepents. Is there a way to adjust your code to generate reciepent with public key only?
My previous code:
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);
And what about Hashing Algorithm? Do i need one or it is only additional protection to ensure that CMS Enveloped Object didnt changed?
FWIW you can use BouncyCastle only to do the DER formatting (plus set the versions, a minor convenience), plus PEM if you want that (also a minor convenience), after you do all the rest of the work yourself. Example:
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
Ok, i figured out how to adjust code to fit my needs so i can share.
Generating recipients:
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;
}
}
AlgorithIdentifier for 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;
}
}
AlgorithmIdentifier for AES CBC (or GCM as well after changing 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;
}
}
And finally generating CMS Enveloped Object:
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);
}

java.security.SignatureException: object not initialized for signing

I'm getting the error:
java.security.SignatureException: object not initialized for signing
at md.update(signature.sign());
when I try to sign my signature. Basically what I'm trying to do is have some data, sign the data with my private key, save it to a file, open the file and check if the signed data is the same as the original data by comparing the two message digests that handle the data. Not really sure if that's the way it's supposed to be done, but I'm just trying different things at the moment.
According to most guides, I'm supposed to initVerify with my pubicKey, update with byte and then sign to check the data, but everytime I try to do the signing I get the exception.
PublicKey publicKey;
boolean verifyData = false;
byte[] sign = null;
MessageDigest md = MessageDigest.getInstance("SHA");
MessageDigest md2 = MessageDigest.getInstance("SHA");
ObjectInputStream keyIn = new ObjectInputStream(new FileInputStream(publicKeyLocation));
publicKey = (PublicKey)keyIn.readObject();
keyIn.close();
BufferedReader reader = new BufferedReader(new FileReader(dataLocation+ ".txt"));
String data = reader.readLine();
reader.close();
ObjectInputStream signatureIn = new ObjectInputStream(new FileInputStream(signatureLocation));
byte[] signatureToVerify = (byte[])signatureIn.readObject();
signatureIn.close();
Signature signature = Signature.getInstance("SHA256withDSA");
signature.initVerify(publicKey);
signature.update(signatureToVerify);
md.update(signature.sign());
md2.update(data.getBytes());;
verifyData = md.digest().equals(md2.digest());
//verifyData = signature.verify(signature.sign());
System.out.println(verifyData);
Here is the verifying class
PrivateKey privateKey;
ObjectInputStream keyIn = new ObjectInputStream(new FileInputStream(privateKeyLocation));
privateKey = (PrivateKey)keyIn.readObject();
keyIn.close();
BufferedReader reader = new BufferedReader(new FileReader(dataLocation+ ".txt"));
String data = reader.readLine();
reader.close();
Signature signature = Signature.getInstance("SHA256withDSA");
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(data.getBytes());
signature.initSign(privateKey);
signature.update(md.digest());
ObjectOutputStream outSignature = new ObjectOutputStream(new FileOutputStream(signatureLocation));
outSignature.writeObject(signature.sign());
outSignature.close();
System.out.println("Finished SignHandler");
and this is the signature initializing signature class.
The privatekey and publickey are handled with a keypairgenerator with the algorithm "DSA" in another class and binary serialized.

how to sign CSR using pkcs11 wrapper ECDSA key

i want to generate ECDSA keypair in pkcs11 usb token. after that want to sign CSR with private key, but facing exception "Invalid signature".
Mechanism keyPairGenerationMechanism = Mechanism.get(PKCS11Constants.CKM_EC_KEY_PAIR_GEN);
ECDSAPrivateKey ecdsaPrivateKeyTemplate = new ECDSAPrivateKey();
ecdsaPrivateKeyTemplate.getLabel().setCharArrayValue(keyAlias.toCharArray());
ecdsaPrivateKeyTemplate.getId().setByteArrayValue(keyAlias.getBytes());
ecdsaPrivateKeyTemplate.getSign().setBooleanValue(Boolean.TRUE);
ecdsaPrivateKeyTemplate.getDecrypt().setBooleanValue(Boolean.TRUE);
ecdsaPrivateKeyTemplate.getToken().setBooleanValue(Boolean.TRUE);
ecdsaPrivateKeyTemplate.getPrivate().setBooleanValue(Boolean.TRUE);
ecdsaPrivateKeyTemplate.getSensitive().setBooleanValue(Boolean.TRUE);
ecdsaPrivateKeyTemplate.getExtractable().setBooleanValue(Boolean.FALSE);
ecdsaPrivateKeyTemplate.getKeyType().setLongValue(PKCS11Constants.CKK_EC); ECDSAPublicKey ecdsaPublicKeyTemplate = new ECDSAPublicKey(); ecdsaPublicKeyTemplate.getLabel().setCharArrayValue(keyAlias.toCharArray());
ecdsaPublicKeyTemplate.getId().setByteArrayValue(keyAlias.getBytes());
ecdsaPublicKeyTemplate.getEncrypt().setBooleanValue(Boolean.TRUE);
ecdsaPublicKeyTemplate.getPrivate().setBooleanValue(Boolean.FALSE);
ecdsaPublicKeyTemplate.getVerify().setBooleanValue(Boolean.TRUE);
ecdsaPublicKeyTemplate.getToken().setBooleanValue(Boolean.TRUE);
ecdsaPublicKeyTemplate.getKeyType().setLongValue(PKCS11Constants.CKK_EC);
ecdsaPublicKeyTemplate.getModifiable().setBooleanValue(Boolean.TRUE);
ASN1ObjectIdentifier curveId = getCurveId((getEcdsaParamsOID(256)));
X962Parameters x962 = new X962Parameters(curveId);
byte[] paramsBytes = x962.getEncoded();
ecdsaPublicKeyTemplate.getEcdsaParams().setByteArrayValue(paramsBytes);
KeyPair generatedKeyPair = m_objSession.generateKeyPair(keyPairGenerationMechanism,ecdsaPublicKeyTemplate, ecdsaPrivateKeyTemplate);
ECDSAPublicKey publicKey = (ECDSAPublicKey) generatedKeyPair.getPublicKey();
ECDSAPrivateKey privateKey = (ECDSAPrivateKey) generatedKeyPair.getPrivateKey();
byte[] pubPoint = publicKey.getEcPoint().getByteArrayValue();
DEROctetString os = (DEROctetString) DEROctetString.fromByteArray(pubPoint);
AlgorithmIdentifier keyAlgID = new AlgorithmIdentifier(
X9ObjectIdentifiers.id_ecPublicKey, curveId);
SubjectPublicKeyInfo pkInfo = new SubjectPublicKeyInfo(keyAlgID, os.getOctets());
Signing code from comments:
ECDSAPrivateKey signatureKey = this.getECDSAPrivateKey(a_strKeyId,m_objSession);
MessageDigest digestEngine = MessageDigest.getInstance("SHA-256");
digestEngine.update(bUnsignedData);
byte[] digest = digestEngine.digest();
Mechanism signatureMechanism = Mechanism.get(PKCS11Constants.CKM_ECDSA);
m_objSession.signInit(signatureMechanism, signatureKey);
DigestInfo digestInfoEngine = new DigestInfo(a_objAlgorithmIdentifier, digest);
byte[] digestInfo = digestInfoEngine.getEncoded();
byte[] signatureValue = m_objSession.sign(digestInfo);
For ECDSA you don't need DigestInfo, the digest value (in bytes) is signed directly. DigestInfo is probably required for RSA.

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.

Getting GPG Decryption To Work In Java (Bouncy Castle)

let me start by saying I'm extremely new to all of this. What I am trying to do is to use gpg from within Java in order to decrypt an encrypted file.
What I've done successfully:
Had a colleague encrypt a file using my public key and his private key and successfully decrypted it.
Went the other way
Had another colleague try to decrypt a file that wasn't for him: fail (as expected)
My key was generated like this...
(gpg --version tells me I'm using 1.4.5 and I'm using Bouncy Castle 1.47)
gpg --gen-ley
Select option "DSA and Elgamal (default)"
Fill in the other fields and generate a key.
The file was encrypted using my public key and another's secret key. I want to decrypt it. I've written the following Java code to accomplish this. I'm using several deprecated methods, but I can't figure out how to properly implement the factory methods required to use the non-deprecated versions, so if anyone has an idea on implementations of those that I should be using that would be a nice bonus.
Security.addProvider(new BouncyCastleProvider());
PGPSecretKeyRingCollection secretKeyRing = new PGPSecretKeyRingCollection(new FileInputStream(new File("test-files/secring.gpg")));
PGPSecretKeyRing pgpSecretKeyRing = (PGPSecretKeyRing) secretKeyRing.getKeyRings().next();
PGPSecretKey secretKey = pgpSecretKeyRing.getSecretKey();
PGPPrivateKey privateKey = secretKey.extractPrivateKey("mypassword".toCharArray(), "BC");
System.out.println(privateKey.getKey().getAlgorithm());
System.out.println(privateKey.getKey().getFormat());
PGPObjectFactory pgpF = new PGPObjectFactory(
new FileInputStream(new File("test-files/test-file.txt.gpg")));
Object pgpObj = pgpF.nextObject();
PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) pgpObj;
Iterator objectsIterator = encryptedDataList.getEncryptedDataObjects();
PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) objectsIterator.next();
InputStream inputStream = publicKeyEncryptedData.getDataStream(privateKey, "BC");
So when I run this code I learn that my algorithm and format are as follows for my secret key:
Algorithm: DSA
Format: PKCS#8
And then it breaks on the last line:
Exception in thread "main" org.bouncycastle.openpgp.PGPException: error setting asymmetric cipher
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.decryptSessionData(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.access$000(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder$2.recoverSessionData(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at TestBouncyCastle.main(TestBouncyCastle.java:74)
Caused by: java.security.InvalidKeyException: unknown key type passed to ElGamal
at org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(Unknown Source)
at org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(Unknown Source)
at javax.crypto.Cipher.init(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
... 8 more
I'm open to a lot of suggestions here, from "don't use gpg, use x instead" to "don't use bouncy castle, use x instead" to anything in between. Thanks!
If anyone is interested to know how to encrypt and decrypt gpg files using bouncy castle openPGP library, check the below java code:
The below are the 4 methods you going to need:
The below method will read and import your secret key from .asc file:
public static PGPSecretKey readSecretKeyFromCol(InputStream in, long keyId) throws IOException, PGPException {
in = PGPUtil.getDecoderStream(in);
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in, new BcKeyFingerprintCalculator());
PGPSecretKey key = pgpSec.getSecretKey(keyId);
if (key == null) {
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
return key;
}
The below method will read and import your public key from .asc file:
#SuppressWarnings("rawtypes")
public static PGPPublicKey readPublicKeyFromCol(InputStream in) throws IOException, PGPException {
in = PGPUtil.getDecoderStream(in);
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in, new BcKeyFingerprintCalculator());
PGPPublicKey key = null;
Iterator rIt = pgpPub.getKeyRings();
while (key == null && rIt.hasNext()) {
PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
Iterator kIt = kRing.getPublicKeys();
while (key == null && kIt.hasNext()) {
PGPPublicKey k = (PGPPublicKey) kIt.next();
if (k.isEncryptionKey()) {
key = k;
}
}
}
if (key == null) {
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
return key;
}
The below 2 methods to decrypt and encrypt gpg files:
public void decryptFile(InputStream in, InputStream secKeyIn, InputStream pubKeyIn, char[] pass) throws IOException, PGPException, InvalidCipherTextException {
Security.addProvider(new BouncyCastleProvider());
PGPPublicKey pubKey = readPublicKeyFromCol(pubKeyIn);
PGPSecretKey secKey = readSecretKeyFromCol(secKeyIn, pubKey.getKeyID());
in = PGPUtil.getDecoderStream(in);
JcaPGPObjectFactory pgpFact;
PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator());
Object o = pgpF.nextObject();
PGPEncryptedDataList encList;
if (o instanceof PGPEncryptedDataList) {
encList = (PGPEncryptedDataList) o;
} else {
encList = (PGPEncryptedDataList) pgpF.nextObject();
}
Iterator<PGPPublicKeyEncryptedData> itt = encList.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData encP = null;
while (sKey == null && itt.hasNext()) {
encP = itt.next();
secKey = readSecretKeyFromCol(new FileInputStream("PrivateKey.asc"), encP.getKeyID());
sKey = secKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
}
if (sKey == null) {
throw new IllegalArgumentException("Secret key for message not found.");
}
InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
pgpFact = new JcaPGPObjectFactory(clear);
PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject();
pgpFact = new JcaPGPObjectFactory(c1.getDataStream());
PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
InputStream inLd = ld.getDataStream();
int ch;
while ((ch = inLd.read()) >= 0) {
bOut.write(ch);
}
//System.out.println(bOut.toString());
bOut.writeTo(new FileOutputStream(ld.getFileName()));
//return bOut;
}
public static void encryptFile(OutputStream out, String fileName, PGPPublicKey encKey) throws IOException, NoSuchProviderException, PGPException {
Security.addProvider(new BouncyCastleProvider());
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName));
comData.close();
PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES).setSecureRandom(new SecureRandom()));
cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey));
byte[] bytes = bOut.toByteArray();
OutputStream cOut = cPk.open(out, bytes.length);
cOut.write(bytes);
cOut.close();
out.close();
}
Now here is how to invoke/run the above:
try {
decryptFile(new FileInputStream("encryptedFile.gpg"), new FileInputStream("PrivateKey.asc"), new FileInputStream("PublicKey.asc"), "yourKeyPassword".toCharArray());
PGPPublicKey pubKey = readPublicKeyFromCol(new FileInputStream("PublicKey.asc"));
encryptFile(new FileOutputStream("encryptedFileOutput.gpg"), "fileToEncrypt.txt", pubKey);
} catch (PGPException e) {
fail("exception: " + e.getMessage(), e.getUnderlyingException());
}
To any one looking for an alternative solution, see https://stackoverflow.com/a/42176529/7550201
final InputStream plaintextStream = BouncyGPG
.decryptAndVerifyStream()
.withConfig(keyringConfig)
.andRequireSignatureFromAllKeys("sender#example.com")
.fromEncryptedInputStream(cipherTextStream)
Long story short: Bouncycastle is programming is often a lot of cargo cult programming and I wrote a library to change that.
I've decided to go with a much different approach, which is to forego the use of bouncy castle altogether and simply use a runtime process instead. For me this solution is working and completely removes the complexity surrounding bouncy castle:
String[] gpgCommands = new String[] {
"gpg",
"--passphrase",
"password",
"--decrypt",
"test-files/accounts.txt.gpg"
};
Process gpgProcess = Runtime.getRuntime().exec(gpgCommands);
BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
BufferedReader gpgError = new BufferedReader(new InputStreamReader(gpgProcess.getErrorStream()));
After doing that you need to remember to drain your input stream as your process is execing or your program will probably hang depending on how much you're outputing. See my answer in this thread (and also that of Cameron Skinner and Matthew Wilson who got me on the proper path) for a bit more context: Calling GnuPG in Java via a Runtime Process to encrypt and decrypt files - Decrypt always hangs
The first Google result is this. It looks like you are trying to decrypt ElGamal data, but you are not passing in an ElGamal key.
There are two easy possibilities:
Your keyring collection has multiple keyrings.
Your keyring has subkeys.
You've picked DSA with ElGamal encryption, so I suspect at least the latter: Subkeys are signed by the master key; ElGamal is not a signing algorithm (I don't know if DSA and ElGamal can use the same key, but it's generally seen as a good idea to use different keys for different purposes).
I think you want something like this (also, secretKeyRing should probably be renamed to secretKeyRingCollection):
PGPSecretKey secretKey = secretKeyRing.getSecretKey(publicKeyEncryptedData.getKeyID());
The error message is difficult because it's not completely accurate. Besides the illegal key size or default parameters the exception doesn't says it could be failing because of crypto permission check fails. That means you haven't setup the JCE permissions properly. You'll need to install the JCE Unlimited Strength Policy.
You can see the debug messages by setting the system property on the jvm
java -Djava.security.debug=access ....

Categories