How to Pack/Encrypt/Unpack/Decrypt a bunch of files in Java? - java

I'm essentially trying to do the following on a Java/JSP-driven web site:
User supplies a password
Password is used to build a strongly-encrypted archive file (zip, or anything else) containing a text file as well as a number of binary files that are stored on the server. It's essentially a backup of the user's files and settings.
Later, the user can upload the file, provide the original password, and the site will decrypt and unpack the archive, save the extracted binary files to the appropriate folder on the server, and then read the text file so the site can restore the user's old settings and metadata about the binary files.
It's the building/encrypting the archive and then extracting its contents that I'm trying to figure out how to do. I really don't care about the archive format, other than that it is very secure.
The ideal solution to my problem will be very easy to implement, and will require only tried-and-tested libraries with free and nonrestrictive licenses (e.g. apache, berkeley, lgpl).
I'm aware of the TrueZIP and WinZipAES libraries; the former seems like massive overkill and I can't tell how stable the latter is... Are there other solutions out there that would work well?

If you know how to create a zip file using the java.util.zip package, you can create a PBE Cipher and pass that to a CipherOutputStream or a CipherInputStream (depending on if you're reading or writing).
The following should get you started:
public class ZipTest {
public static void main(String [] args) throws Exception {
String password = "password";
write(password);
read(password);
}
private static void write(String password) throws Exception {
OutputStream target = new FileOutputStream("out.zip");
target = new CipherOutputStream(target, createCipher(Cipher.ENCRYPT_MODE, password));
ZipOutputStream output = new ZipOutputStream(target);
ZipEntry e = new ZipEntry("filename");
output.putNextEntry(e);
output.write("helloWorld".getBytes());
output.closeEntry();
e = new ZipEntry("filename1");
output.putNextEntry(e);
output.write("helloWorld1".getBytes());
output.closeEntry();
output.finish();
output.flush();
}
private static Cipher createCipher(int mode, String password) throws Exception {
String alg = "PBEWithSHA1AndDESede"; //BouncyCastle has better algorithms
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(alg);
SecretKey secretKey = keyFactory.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede");
cipher.init(mode, secretKey, new PBEParameterSpec("saltsalt".getBytes(), 2000));
return cipher;
}
private static void read(String password) throws Exception {
InputStream target = new FileInputStream("out.zip");
target = new CipherInputStream(target, createCipher(Cipher.DECRYPT_MODE, password));
ZipInputStream input = new ZipInputStream(target);
ZipEntry entry = input.getNextEntry();
while (entry != null) {
System.out.println("Entry: "+entry.getName());
System.out.println("Contents: "+toString(input));
input.closeEntry();
entry = input.getNextEntry();
}
}
private static String toString(InputStream input) throws Exception {
byte [] data = new byte[1024];
StringBuilder result = new StringBuilder();
int bytesRead = input.read(data);
while (bytesRead != -1) {
result.append(new String(data, 0, bytesRead));
bytesRead = input.read(data);
}
return result.toString();
}
}

The answer is already given (use a cipher as Kevin pointed out), so I am only doing a suggestion about an important matter which seems to be missing in your topicstart: ensure that you're using HTTPS instead of HTTP. Otherwise one with a network sniffer would be able to get the user-supplied password from the packets. How to do it depends on the appserver in question. Best is to refer its documentation. If it is for example Apache Tomcat, then you can find everything in the Tomcat SSL HOW-TO.
Hope this helps.

Though it may not be specific to your query I wonder if truecrypt could be of use. Your webserver could create an encrypted container into which the zip file would be copied. The encrypted container could then be downloaded. Potentially a little messy however the encryption should be strong and the downloaded image could be mounted on a variety of operating systems.

There are surely a few suggestions here on how to solve your problem, but I'm missing a very big BUT in the responses. You cannot fulfill both "password based" and "strong encryption" for any reasonable definition of "strong encryption".

Related

CMS encryption on Android 9 and higher

Google announced this year that it's deprecating some functionality from the BC provider. I am using CMS to encrypt the text using a certificate of X509Certificate type. However, on Android Pie, I encountered NoSuchAlgorithmException as mentioned in the Google post earlier.
For apps targeting Android P or later, the call will throw
NoSuchAlgorithmException.
To resolve this, you should stop specifying a provider and use the
default implementation.
I also checked Conscrypt capabilities here, but couldn't find a way to perform CMS on the text received. Is there any other alternative that can help achieve the same level/type of encryption?
Here's my code:
public byte[] encryptText(String txt) {
CMSEnvelopedDataStreamGenerator cmsdataGen = new CMSEnvelopedDataStreamGenerator();
cmsdataGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(deviceCert).setProvider("BC"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputStream out = cmsdataGen.open(byteArrayOutputStream, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider("BC").build());
byte[] unencryptedContent = txt.getBytes();
out.write(unencryptedContent);
String encrypted = byteArrayOutputStream.toString();
Log.v("Security","ENCRYPTED_STR BEFORE ENCODING= " + encrypted);
out.close();
return byteArrayOutputStream.toByteArray();
}

InvalidKeyException: Key Spec Not Recognised

I'm getting a
InvalidKeyException: Illegal key size or default parameters
when trying to run a web app that is was a deployed WAR. I am hosting it on Tomcat in a Linux environment. I have already put the two UnlimitedJCEPolicy files into the destination /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.55.x86_64/jre/lib/security, and it seems that the error is still occuring. NOTE that this only is thrown when I am running in the linux environment. Locally, it works fine. Here is my code:
public static final void decryptFile(File inputFile, File outputFile) throws
IOException, PGPException {
// Add Bouncy Castle provider
Security.addProvider(new BouncyCastleProvider());
// Grab secret key that's in folder with AE classes
Resource resource = new ClassPathResource(Env.getSecretKeyAE());
log.debug("Resource: " + Env.getSecretKeyAE());
File keyFileName = resource.getFile();
log.debug("Key File Name: " + keyFileName);
// Decryption password
String pass = "pass";
char[] passwd = pass.toCharArray();
// Read files into streams
log.info("Reading files into streams");
InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
InputStream in = PGPUtil.getDecoderStream(new BufferedInputStream(new
FileInputStream(inputFile)));
// I don't even know what these do
PGPObjectFactory pgpObjFactory = new PGPObjectFactory(in);
PGPEncryptedDataList pgpEncryptedDataList = null;
Object o = pgpObjFactory.nextObject();
log.info("Checking instance of PGPEncryptedDataList");
if (o instanceof PGPEncryptedDataList) {
pgpEncryptedDataList = (PGPEncryptedDataList)o;
}
else {
pgpEncryptedDataList = (PGPEncryptedDataList)pgpObjFactory.nextObject();
}
// This will be the PGPPrivateKey we use to decrypt
log.info("Initializing secret key");
PGPPrivateKey secretKey = null;
PGPPublicKeyEncryptedData publicKeyEncryptedData = null;
PGPSecretKeyRingCollection pgpSecretKeyRingCollection = new
PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
// This iterates the key file as if it has many keys, this file has only one
// This is the only way I could find to construct a PGPPrivateKey
log.info("Iterating through key file");
Iterator<?> it = pgpEncryptedDataList.getEncryptedDataObjects();
while(it.hasNext() && secretKey == null) {
publicKeyEncryptedData = (PGPPublicKeyEncryptedData) it.next();
PGPSecretKey pgpSecKey =
pgpSecretKeyRingCollection.getSecretKey(publicKeyEncryptedData.getKeyID());
if (pgpSecKey != null) {
Provider provider = Security.getProvider("BC");
secretKey = pgpSecKey.extractPrivateKey(new
JcePBESecretKeyDecryptorBuilder(new
JcaPGPDigestCalculatorProviderBuilder().setProvider(provider)
.build()).setProvider(provider).build(passwd));
}
}
log.info("PGPPrivateKey has been constructed");
if (secretKey == null) {
throw new IllegalArgumentException("secret key for message not found.");
}
log.info("Secret Key found!");
if(publicKeyEncryptedData == null) {
throw new NullPointerException("cannot continue with null public key encryption
data.");
}
log.info("Public Key Encrypted Data found!");
// More stuff I don't fully understand, I think this is just standard way to
decrypt files once the above is all set up
log.info("Starting actual decryption");
//get data stream where our publicKeyDataDecrypterFactory sets ours provider to BC
and we build our secretKey
//secretkey is our PGPPrivateKey
log.info("start");
//=====================================================================
//ERROR IS OCCURRING HERE
InputStream clear = publicKeyEncryptedData.getDataStream(new
JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(secretKey));
log.info("1");
PGPObjectFactory plainFact = new PGPObjectFactory(clear);
log.info("2");
PGPCompressedData compressedData = (PGPCompressedData)plainFact.nextObject();
log.info("3");
InputStream compressedStream = new
BufferedInputStream(compressedData.getDataStream());
log.info("4");
PGPObjectFactory pgpFact = new PGPObjectFactory(compressedStream);
log.info("5");
Object message = pgpFact.nextObject();
log.info("6");
if (message instanceof PGPLiteralData) {
log.info("Our message is an instance of PGP Literal Data.");
PGPLiteralData literalData = (PGPLiteralData)message;
InputStream literalDataInputStream = literalData.getInputStream();
OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFile));
Streams.pipeAll(literalDataInputStream, out);
out.close();
}
else if (message instanceof PGPOnePassSignatureList) {
log.error("encrypted message contains a signed message - not literal data.");
throw new PGPException("encrypted message contains a signed message - not
literal data.");
}
else {
log.error("message is not a simple encrypted file - type unknown.");
throw new PGPException("message is not a simple encrypted file - type
unknown.");
}
log.info("Checking if public key encrypted data is integrity protected");
if (publicKeyEncryptedData.isIntegrityProtected()) {
if (!publicKeyEncryptedData.verify()) {
throw new PGPException("message failed integrity check");
}
}
keyIn.close();
in.close();
}
Using logs, I was able to find that the error was occurring when
InputStream clear = publicKeyEncryptedData.getDataStream(new
JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(secretKey));
But I have no clue why. Like I said, I already placed the JCEUnlimited files appropriately and the error still occurs.
EDIT I fixed the illegal key size problem, but am now getting "key spec not recognized"
EDIT More elaboration on the error "key spec not recognized":
So as I said, Illegal key size is gone, but "key spec not recognized" seems to be a problem still. The weird thing is that my encryptFile method works perfectly, but decryptFile is throwing the error. I'm not entirely sure why. Before I left work, I tested one more time and it seems that the error wasn't thrown. I almost seems like this error occurs randomly depending on the deployment of the WAR to tomcat. If I deploy my WAR, the error wont occur at some points, but if I undeploy and redeploy with an updated WAR file, the error is thrown. I have no clue what is causing this, and the based off research no one really knows either. Apparently this used to be a bug in Bouncy Castle before 1.5, but 1.5 is the version I'm running so that's not the problem. I will post if I find anything that can possibly fix this error.
To solve:
java.security.spec.InvalidKeySpecException: key spec not recognised
Modify security providers:
sudo nano $JAVA_HOME/jre/lib/security/java.security
Add security provider:
security.provider.10=org.bouncycastle.jce.provider.BouncyCastleProvider
Copy bcprov-jdk15on-1.54.jar to:
$JAVA_HOME/jre/lib/ext/bcprov-jdk15on-1.54.jar
Restart Tomcat.
if (o instanceof PGPEncryptedDataList) {
pgpEncryptedDataList = (PGPEncryptedDataList)o;
if o is already an instance of PGPEncryptedDataList, why are you casting it to PGPEncryptedDataLIST?
I don't know enough about the specifics of what you're doing so I just figured I'd provide some general code analysis. Sorry I couldn't be of more help.
To prevent the error "Illegal key size or default parameters", I simply had to put the UnlimitedJCEPolicy files in my working java directory, /opt/jre1.7.0_60/lib/security. After putting the files there, and redeploying my war file, I was not experiencing this problem anymore.
To prevent the "key spec not recognized" error, I had to restart my tomcat server when redeploying my WAR file.

When and why decorate OutputStream with ArmoredOutputStream when using BouncyCastle

I'm pretty new to BouncyCastle and pgp. I've seen many articles and samples on the internet. Almost every encryption sample contains the code snipped below
if (armor)
out = new ArmoredOutputStream(out);
It seems that my local test passed with both armor and none-armor. I googled around but found few useful and the javadoc of ArmoredOutputStream only shows This is basic output stream.
So what's the difference and when to use it?
Complete code sample:
public static void encryptFile(String decryptedFilePath,
String encryptedFilePath,
String encKeyPath,
boolean armor,
boolean withIntegrityCheck)
throws Exception{
OutputStream out = new FileOutputStream(encryptedFilePath);
FileInputStream pubKey = new FileInputStream(encKeyPath);
PGPPublicKey encKey = readPublicKeyFromCollection2(pubKey);
Security.addProvider(new BouncyCastleProvider());
if (armor)
out = new ArmoredOutputStream(out);
// Init encrypted data generator
PGPEncryptedDataGenerator encryptedDataGenerator =
new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, withIntegrityCheck, new SecureRandom(),"BC");
encryptedDataGenerator.addMethod(encKey);
OutputStream encryptedOut = encryptedDataGenerator.open(out, new byte[BUFFER_SIZE]);
// Init compression
PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
OutputStream compressedOut = compressedDataGenerator.open(encryptedOut);
PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
OutputStream literalOut = literalDataGenerator.open(compressedOut, PGPLiteralData.BINARY, decryptedFilePath, new Date(), new byte[BUFFER_SIZE]);
FileInputStream inputFileStream = new FileInputStream(decryptedFilePath);
byte[] buf = new byte[BUFFER_SIZE];
int len;
while((len = inputFileStream.read(buf))>0){
literalOut.write(buf,0,len);
}
literalOut.close();
literalDataGenerator.close();
compressedOut.close();
compressedDataGenerator.close();
encryptedOut.close();
encryptedDataGenerator.close();
inputFileStream.close();
out.close();
}
}
ArmoredOutputStream uses an encoding similar to Base64, so that binary non-printable bytes are converted to something text friendly. You'd do this if you wanted to send the data over email, or post on a site, or some other text medium.
It doesn't make a difference in terms of security. There is a slight expansion of the message size though. The choice really just depends on what you want to do with the output.
ASCII armor is a generic term that means a binary data representation as an ASCII-only text. Technically, there is a lot of ways to ascii-armor binary data, but in the cryptography-related field the PEM format is prevalent (also check this and related questions at serverfault).
The PEM is basically a Base64-encoded binary data wrapped in -----BEGIN SOMETHING----- and -----END SOMETHING----- delimiters and a set of additional headers that can contain some meta information about the binary content.

Why encrypted and base64 encoded text appears different on Windows and Linux

I have a legacy system that uses hibernate interceptor to encrypt (and encode) and decrypt (and decode) some fields on some database tables. It makes use of the OnSave, OnLoad and OnFlushDirty methods. This code turns out to be buggy as data read from this system, when transferred to another application still has some of the records encrypted and encoded (some encrypted multiple times). The challenge for me here is that I could perform the decryption and decoding (as many times as necessary) when the receiving application is on a Windows machine. I get a BadPaddingException when I try to repeat the same thing when the receiving application is a linux VM.
Any help/suggestions will be greatly appreciated
here is a snippet of the hibernate interceptor
public boolean onLoad(Object entity, Serializable arg1, Object[] state, String[] propertyNames, Type[] arg4) throws CallbackException {
if (key != null){
try {
if (entity instanceof BasicData) {
for (int i = 0; i < state.length; i++) {
if (state[i] instanceof String){
String cipherText = (String)state[i];
byte[] cipherTextBytes = Base64Coder.decode(cipherText);
byte[] plainTextBytes = dCipher.doFinal(cipherTextBytes);
state[i] = new String(plainTextBytes, "UTF8");
}
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}}return false;}
I'd have to guess here but if you mean this Base64Coder the problem might be the following:
It is unclear how the base64 string has been created, i.e. which encoding had been used.
If you use UTF-8 to get the bytes of a string and create a base64 from those bytes you'll get a different result than if you'd use ISO Latin-1, for example.
Afterwards you create a string from those bytes using UTF-8, but if the base64 string had not been created using UTF-8, you'll get wrong results.
Just a quote from the linked source (if this is the correct one):
public static String encodeString (String s) {
return new String(encode(s.getBytes())); }
Here, s.getBytes() will use the system's/jvm's default encoding, so you should really ensure it is UTF-8!
If you control both sides, encode and decode, better way to use DatatypeConverter:
String buffer = DatatypeConverter.printBase64Binary( symKey );
byte[] supposedSymKey = DatatypeConverter.parseBase64Binary( buffer );

Verify file is copied in Java

I'm working on moving some files to a different directory in my project and it's working great, except for the fact that I can't verify it's moved properly.
I want to verify the length of the copy is the same as the original and then I want to delete the original. I'm closing both FileStreams before I do my verification but it still fails because the sizes are different. Below is my code for closing the streams, verification and deletion.
in.close();
out.close();
if (encCopyFile.exists() && encCopyFile.length() == encryptedFile.length())
encryptedFile.delete();
The rest of the code before this is using a Util to copy the streams, and it's all working fine so really I just need a better verification method.
One wonderful way you can check is to compare md5 hashes. Checking file length doesn't mean they are the same. While md5 hashes doesn't mean they are the same either, it is better than checking the length albeit a longer process.
public class Main {
public static void main(String[] args) throws NoSuchAlgorithmException, IOException {
System.out.println("Are identical: " + isIdentical("c:\\myfile.txt", "c:\\myfile2.txt"));
}
public static boolean isIdentical(String leftFile, String rightFile) throws IOException, NoSuchAlgorithmException {
return md5(leftFile).equals(md5(rightFile));
}
private static String md5(String file) throws IOException, NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("MD5");
File f = new File(file);
InputStream is = new FileInputStream(f);
byte[] buffer = new byte[8192];
int read = 0;
try {
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
String output = bigInt.toString(16);
return output;
} finally {
is.close();
}
}
}
You could include a checksum in your copy operation. Perform a checksum on the destination file and see that it matches a checksum on the source.
You could use commons io:
org.apache.commons.io.FileUtils.contentEquals(File file1, File file2)
or you could use checksum methods:
org.apache.commons.io.FileUtils:
static Checksum checksum(File file, Checksum checksum) //Computes the checksum of a file using the specified checksum object.
static long checksumCRC32(File file) //Computes the checksum of a file using the CRC32 checksum routine.
If you get no exception while copying streams, you should be OK. Make sure you don't ignore exceptions thrown by close method!
Update: If you use FileOutputStream, you can also make sure everything was written properly by calling fileOutputStream.getFD().sync() before closing your fileOutputStream.
Of course, if you want to absolutely make sure that files are the same, you can compare their checksums/digests, but that sounds bit paranoid to me.
If the sizes are different, perhaps you are not flushing the output stream before closing it.
Which file is bigger? What are the sizes of each file? Have you actually looked at the two files to see what is different?

Categories

Resources