How to test signature verification failure using bouncy castle - java

I have a java program that has a method:
static void verifyAndDecrypt(PGPPublicKey publicKeyIn, PGPSecretKeyRingCollection privateKey, String password, InputStream toBeDecrypted, OutputStream outputStream)
It uses bouncy castle to decrypt and verify a file. The output is written to outputStream.
In production I have run into the scenario of receiving files with signatures that the java code is able to decrypt, but not do signature verification on.
Lets call such a production file prodFile.txt.gpg.
That file is thus encrypted with our public key, and signed with the senders private key.
(I have yet to figure out why it fails, these files are able to be verified using GnuPG)
I have added the option of being lenient and just log a warning when signature fails.
My question is, how do I write unit tests for this?
I could (and have locally on my machine) write a test that takes prodFile.txt.gpg and call verifyAndDecrypt() on it, given that I pass in our secret key and password, and asserts that the file is decrypted, and when the signature verification fails logs a warning.
But I can't check in this code, since it would leave password and private keys in the repo.
So my question is,
How do I create a file testFile.txt.gpg that replicates the above scenario?
I could create key pairs for testing, but I don't know how to force the failing of the signature in the same way as with prodFile.txt.gpg.
I am also wondering if it's possible to "unpack" the content of prodFile.txt.gpg so that I have the cleartext file: prodFile.txt and the corresponding signature that I have problem with. And from there make a modification to prodfile.txt so that the signature doesn't match, then encrypt it with the public key of a keypair for testing purposes while using the "bad" signature. But I can't seem to find a way to do this. I suspect the gpg command won't let me do something like this.

Related

Is there any way to get the plain text from signed data using private key?

The plain text is signed using java.security.Signature. Below is the code used to sign the plain text
public String getSignature(String plainText) throws Exception
{
KeyStore keyStore = loadKeyStore(); // A local method to read the keystore file from file system.
PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS_IN_KEYSTORE, KEYSTORE_PASSWORD.toCharArray());
Signature privateSignature = Signature.getInstance(SIGNATUREALGO);
privateSignature.initSign(privateKey);
privateSignature.update(plainText.getBytes("UTF-8"));
byte[] signature = privateSignature.sign();
return String.valueOf(signature);
// KEY_ALIAS_IN_KEYSTORE, KEYSTORE_PASSWORD and SIGNATUREALGO are all constant Strings
}
Note 1: I found online a way to verify the signature using the public key Java Code Examples for java.security.Signature#verify(). But this is not what I require.
Note 2: I also found a ways to encrypt and decrypt as mentioned here RSA Signing and Encryption in Java. But the use case I have in hand is to get the original plain text from a signed data. Is that possible?
No, you can't retrieve the original content from just the signature.
The signature alone does not contain enough information to restore the original clear text, no matter what keys you have access to.
The basic idea of a signature is to send it together with the clear text. That means the clear text will be visible, but the signature can be used to verify that the message was written (or at least signed) by who claims to have done so and has not been tampered with since then.
Signing something is different from encrypting it. The two often uses the same or related technologies and both fall under cryptography.

OpenSSL command equivalent using JAVA

So I have a very basic openssl command that was provided to me openssl smime -encrypt -binary -aes-256-cbc -in $inPath -out $encryptedPath -outform DER $pubCert, this command also works correctly and outputs an encrypted file. I need to use the equivalent of this command in a java application, preferably without invoking process and using openssl itself (only because I feel like that is probably bad practice).
I have researched quite a lot and there does not seem to be any equivalent out there that I can find.. I have tried several things and most of them do not seem to work. The weird thing is... I am able to get a simple "Hello World" string to encrypt using the code I wrote (although I don't believe it was encrypting it correctly because I had the cipher set to "RSA" not "AES") but when the byte array was coming from a file, it silently failed and just wrote 0 bytes. Right now this is what my code looks like.
Cipher aes = Cipher.getInstance("RSA");
CertificateFactory certF = CertificateFactory.getInstance("X.509");
File public_cert = new File( getClass().getClassLoader().getResource("public.crt").getFile());
FileInputStream certIS = new FileInputStream(public_cert);
X509Certificate cert = (X509Certificate) certF.generateCertificate(certIS);
certIS.close();
aes.init(Cipher.ENCRYPT_MODE, cert);
File tarGz = new File("C:\\volatile\\generic.tar.gz");
FileInputStream fis = new FileInputStream(tarGz);
byte[] tarGzBytes = FileUtils.readFileToByteArray(tarGz);
tarGzBytes = "Hello World".getBytes();
ByteArrayInputStream bais = new ByteArrayInputStream("Hello World".getBytes());
File encFile = new File("C:\\volatile\\generic.tar.gz.enc");
FileOutputStream enc = new FileOutputStream(encFile);
CipherOutputStream cos = new CipherOutputStream(enc, aes);
cos.write(tarGzBytes);
//IOUtils.copy(fis, cos);
//IOUtils.copy(bais, cos);
cos.flush();
cos.close();
So this works, and encrypts a little file with Hello World encrypted in it. I don't believe this is AES-256-CBC though, and it does not work when I use the FileUtils.readFileToByteArray(tarGz), although the resulting byte array in a debugger is correctly sized at about 94MB. Which seems really odd to me, that it works with "Hello World".toByteArray() and not FileUtils.readAllBytes(tarGz). Also as a side note, the ByteArrayInputStream using IOUtils.copy works, whereas the FileInputStream version writes 0 bytes as well.
Also, when I set the cipher mode to AES/CBC/PKCS5Padding (because I found something online suggesting to set it to that and it looks more like what I want) I get the following error message:
java.security.InvalidKeyException: No installed provider supports this key: sun.security.rsa.RSAPublicKeyImpl
at javax.crypto.Cipher.chooseProvider(Cipher.java:892)
at javax.crypto.Cipher.init(Cipher.java:1724)
~~~~
If anyone has any suggestions, or if I need to provide more information please let me know. I am fairly stuck right now and I am at this point debating writing a script to simply run the openssl command and run that script from java...
Conclusion
After reading through #dave-thompson-085's answer I realized that there was a really good reason why I could not find what I was wanting to do. So therefore I decided to go ahead and just call the openssl process from java using a process builder. I was able to recreate the openssl command from above as a Process in java, start it and run it with the following code:
File cert = new File(getClass().getClassLoader().getResource("public.crt").getFile());
ProcessBuilder openSslBuilder = new ProcessBuilder("openssl", "smime", "-encrypt", "-binary",
"-aes-256-cbc", "-in", "C:\\volatile\\generic.tar.gz", "-out",
"C:\\volatile\\generic.tar.gz.enc", "-outform", "DER", cert.getPath());
Process openssl = openSslBuilder.start();
openssl.waitFor();
System.out.println(openssl.exitValue());
openssl.destroy();
Hopefully this helps someone else who is looking to attempt this as well and maybe save someone a bunch of time!
First, to be clear: the openssl smime command actually handles both S/MIME and CMS (aka PKCS7) formats; these are related but different standards that basically use different file formats for essentially the same cryptographic operations. With -outform DER you are actually doing CMS/PKCS7.
Second and more fundamental: CMS/PKCS7, and S/MIME, and most other common cryptographic schemes like PGP, actually does hybrid encryption. Your data is not actually encrypted with RSA; instead your data is encrypted with a symmetric algorithm (here AES-256-CBC, since you selected that) using a randomly generated key called the DEK (data encryption key) and the DEK is encrypted with RSA using the recipient's publickey (obtained from their certificate), and both of those results plus a good deal of metadata is arranged into a fairly complicated data structure. The recipient can parse the message to extract these pieces, then use RSA with their privatekey to decrypt the DEK, then AES-decrypt the data with the DEK. Note you always use RSA keys for RSA, and AES keys for AES; symmetric keys are pretty much all just bits and only vary in size, but public-key cryptographic keys including RSA (also DH, DSA, ECC and more) are much more complicated and cannot be intermixed.
Trying to encrypt data directly with RSA as you did, in addition to being wrong, won't work in general because RSA can only encrypt limited amounts of data, depending on the key size used, typically about 100-200 bytes. Symmetric encryption also has some limits, but they are generally much larger; AES-CBC is good for about 250,000,000,000,000,000 bytes.
If you want to implement this yourself, you need to read the standard for CMS particularly the section on EnvelopedData using KeyTransRecipientInfo (for RSA), combined with the rules for ASN.1 BER/DER encoding. This is not a simple job, although it can be done if you want to put the effort in.
If you can use a third-party library in Java, the 'bcpkix' jar from https://www.bouncycastle.org has routines that support CMS, among several other things. This is usually easy if you are writing a program to run yourself, or in your department. If this is to be delivered to outside users or customers who may not like having to manage a dependency, maybe not.
That said, running another program to do something isn't necessarily bad practice in my book, and can be done directly from java (no script). Unless you (need to) do it very often, such as 100 times a second.

Why does the Java KeyStore fail at loading an OpenPGP key?

I am willing to spend some amount of time developing yet another license manager for desktop Java application. After some looking around I discovered JCPUID by Iakin that is free to use and should work at most operating systems with native libs that I found here.
My idea is to do two modules: main application that will show popup window with CPU ID and verification text field and key generator app. User will pass CPU ID to keygen owner, who will return verification code (generated with keygen) to user. After user submits correct verification code, license file with that code will be created at filesystem. Every time the application starts up, it will check the existence and correctness of that file and load main application screen after that.
What about code verification, I think the best option will be to use asymmetric cryptography, in particular RSA. The public key will be built-in into application and secret will be built-in into key generator. So CPUID will be passed to key generator owner and then signed with RSA. That signature will be transferred back to user, who will verify its validity with built-in public key.
I generated gpg key pairs using Kleopatra and gpg Linux command line tool itself. Then I tried to sign something using this method:
private byte[] createSignature(byte[] file) {
byte[] signature = null;
try {
java.security.KeyStore keyStoreFile = java.security.KeyStore
.getInstance("PKCS12");
keyStoreFile.load(getClass().getClassLoader().getResourceAsStream("/secret.asc"),
"******".toCharArray());
PrivateKey privateKey = (PrivateKey) keyStoreFile.getKey(
"My Name Here", "******".toCharArray());
Signature dsa = Signature.getInstance("SHA1withRSA");
dsa.initSign(privateKey);
dsa.update(file, 0, file.length);
signature = dsa.sign();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return signature;
}
But the privateKey initialization throws exception:
java.security.InvalidKeyException: Key must not be null
I guess it's because of wrong instance format here:
java.security.KeyStore keyStoreFile = java.security.KeyStore
.getInstance("PKCS12");
I would like to know:
How good is this approach at all?
What difference exists between different OpenPGP key formats and which will be the best to use at this case? How to know the format of existing OpenPGP file?
The Java crypto framework does not support OpenPGP. X.509 keys, for example in the PKCS12 format, are incompatible with OpenPGP -- although they rely on (mostly) the same cryptographic algorithms.
Either use X.509 certificates (you could also create your own CA for this purpose), or rely on an implementation of OpenPGP for Java. In terms of open source libraries, you can choose between the native Java implementation BouncyCastle (MIT license), or interface GnuPG (GPL) through the Java GPGME binding (LGPL).
BouncyCastle is probably the better way to go, as all you need to do is add another Java library, not install another software into the system.

Failure to open JCEKS keystore with pyjks

I'm trying to use the pyjks module to grab keys from a keystore, however loading the keystore fails with the following error:
ValueError: Hash mismatch; incorrect password or data corrupted
If I try using keytool to load the keystore, I have no issues. I was wondering if anyone has ever used pyjks to do this and done so successfully. Here's my python code snippet:
ks = jks.KeyStore.load("/tmp/keystore.jceks", "changeit")
Disclaimer: I wrote the initial JCEKS support for pyjks.
This might be due to the lack of support for SecretKey entries at the time. The parsing routine tracks the current position in the file as it reads through it, and at the end expects the next N bytes to be the correct signature. Because SecretKeys were not yet implemented, they did not advance the current position, thus causing a bad hash check.
I'm responding because I recently added the missing SecretKey support to pyjks. So if your situation is still relevant, feel free to grab the latest source from https://github.com/doublereedkurt/pyjks and try it out.

Java cryptography object in file password

i try to find the good way for the best technology/method for hidden password in a file, but without use external jar or library.
Actually i use one object that represent a list of user name and password. Convert my list in a xml (only in memory) and after that, i store in a file with AES.
Use only java 7, no external library.
Is a good/secure method?
If this operation is no good, is possible to create dynamically xml encrypted?
thanks
You can use a FileOutputStream wrapped in a CipherOutputStream.
It's not really secure to save passwords encrypted with AES because:
1) Where do you store the key? If you store it in the server, if an attacker violates the server and finds the key, he will have complete acces to the users information.
2) Do you really need to know the users' passwords? In many application, for security reasons, it's better to keep only the hash of the password. The username can be stored in plaintext and you can also add a salt to the password to enforce it. You can do that with some algorithms offered by Java7 platform. In this way, even if someone enters your server, he can't use users login informations without breaking the hash function.
Here's an example that worked for me:
public byte[] getHash(String password, byte[] salt, String algorithm) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest digest = MessageDigest.getInstance(algorithm);//The String rapresents the alg you want to use: for example "SHA-1" or "SHA-256"
digest.reset();
digest.update(salt);
return digest.digest(password.getBytes("UTF-8"));
}
You can also look at this link for a more complete example: https://www.owasp.org/index.php/Hashing_Java

Categories