I'm trying to 3DES encrypt a string and store it in a properties file using this example. The problem I'm having is I do not want to write the contents of encrypt() and decrypt() to a file directly from the methods. I want to store it in a string for use later.
Below are the methods I'm using.
As you can see this uses CipherOutputStream and CipherInputStream. How would I read the result of both encrypt() and decrypt() into a String instead of writing it out to file?
public static void encrypt(SecretKey key, InputStream in, OutputStream out)
throws NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, IOException {
// Create and initialize the encryption engine
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.ENCRYPT_MODE, key);
// Create a special output stream to do the work for us
CipherOutputStream cos = new CipherOutputStream(out, cipher);
// Read from the input and write to the encrypting output stream
byte[] buffer = new byte[2048];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
}
cos.close();
// For extra security, don't leave any plaintext hanging around memory.
java.util.Arrays.fill(buffer, (byte) 0);
}
/**
* Use the specified TripleDES key to decrypt bytes ready from the input
* stream and write them to the output stream. This method uses uses Cipher
* directly to show how it can be done without CipherInputStream and
* CipherOutputStream.
*/
public static void decrypt(SecretKey key, InputStream in, OutputStream out)
throws NoSuchAlgorithmException, InvalidKeyException, IOException,
IllegalBlockSizeException, NoSuchPaddingException,
BadPaddingException {
// Create and initialize the decryption engine
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.DECRYPT_MODE, key);
// Read bytes, decrypt, and write them out.
byte[] buffer = new byte[2048];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(cipher.update(buffer, 0, bytesRead));
}
// Write out the final bunch of decrypted bytes
out.write(cipher.doFinal());
out.flush();
}
Simple: instead of passing a file output stream to these methods - pass a different kind of stream, for example a ByteArrayOutputStream.
Then you can extract the encrypted data as string from that stream.
And to ensure that the final result is reasonable encoded, you should actually use some kind of Base64OutputStream which in turn writes to that ByteArrayOutputStream.
Related
I'm trying to encrypt/decrypt some files, which I will be reading/writing using FileIn/OutputStreams piped through CipherIn/OutputStreams. Fairly simple in concept, and I've gotten this to work using raw byte arrays and Cipher.doFinal. So I know my encryption parameters (bit size, iv size, etc.) are correct. (Or at least functional?)
I'm able to write data through a CipherOutputStream just fine. However, when I try to read that data back through a CipherInputStream, it hangs indefinitely.
The only related problem I've found remains unanswered, and is potentially fundamentally different from my problem, as my problem will always have all the data available on disk, as opposed to the related problem's reliance on Sockets.
I've tried a number of solutions, the most obvious one being changing the buffer size (data = new byte[4096];). I've tried a number of values, including the size of the plaintext and the size of the encrypted data. None of these values work. The only solution I've found is avoiding using a CipherInputStream altogether, and instead relying on Cipher.doFinal and Cipher.update.
Am I missing something? It would be very nice to be able to use a CipherInputStream, rather than having to reinvent the wheel by using Cipher.update.
SSCCE:
private static final String AES_ALG = "aes_256/gcm/nopadding";
private static final int GCM_TAG_SIZE = 128;
private static void doEncryptionTest() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, FileNotFoundException, IOException
{
File f = new File("encrypted_random_data.dat");
// 12-byte long iv
byte[] iv = new byte[] {0x27, 0x51, 0x34, 0x14, -0x65, 0x4d, -0x67, 0x35, -0x63, 0x11, -0x02, -0x05};
// 256-bit long key
byte[] keyBytes = new byte[] {0x55, -0x7f, -0x17, -0x29, -0x68, 0x25, 0x29, 0x5f, -0x27, -0x2d, -0x4d, 0x1b,
0x25, 0x74, 0x57, 0x35, -0x23, -0x1b, 0x12, 0x7c, 0x1, -0xf, -0x60, -0x42, 0x1c, 0x61, 0x3e, -0x5,
-0x13, 0x31, -0x48, -0x6e};
SecretKey key = new SecretKeySpec(keyBytes, "AES");
OutputStream os = encryptStream(key, iv, f);
System.out.println("generating random data...");
// 24MB of random data
byte[] data = new byte[25165824];
new Random().nextBytes(data);
System.out.println("encrypting and writing data...");
os.write(data);
os.close();
InputStream is = decryptStream(key, iv, f);
System.out.println("reading and decrypting data...");
// read the data in 4096 byte packets
int n;
data = new byte[4096];
while ((n = is.read(data)) > 0)
{
System.out.println("read " + n + " bytes.");
}
is.close();
}
private static OutputStream encryptStream(SecretKey key, byte[] iv, File f) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, FileNotFoundException
{
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
Cipher enc = Cipher.getInstance(AES_ALG);
enc.init(Cipher.ENCRYPT_MODE, key, spec);
OutputStream os = new CipherOutputStream(new FileOutputStream(f), enc);
return os;
}
private static InputStream decryptStream(SecretKey key, byte[] iv, File f) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, FileNotFoundException
{
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
Cipher dec = Cipher.getInstance(AES_ALG);
dec.init(Cipher.DECRYPT_MODE, key, spec);
InputStream is = new CipherInputStream(new FileInputStream(f), dec);
return is;
}
It doesn't hang, it's just very slow. The CipherInputStream has a fixed input buffer of size 512, meaning it invokes the Cipher#update(byte[], int, int) method with at most 512 bytes at a time. Decrypting manually with bigger buffer sizes makes it a lot faster.
The reason is that calling update 50 000 times with 512 bytes takes a lot longer than calling it, say, 400 times with 65 kilobytes. I'm not sure why exactly, but there seems to be a constant overhead that you have to pay for every call to update, regardless of the amount of data you pass it.
Additionally be aware that you cannot use AES GCM to decrypt large files. By design, Sun's implementation of the cipher buffers the whole ciphertext in memory before decrypting it. You'd have to split the plaintext into small enough chunks and encrypt each chunk individually.
See also https://crypto.stackexchange.com/questions/20333/encryption-of-big-files-in-java-with-aes-gcm and How come putting the GCM authentication tag at the end of a cipher stream require internal buffering during decryption?.
I have this method for encryption:
private byte[] encrypt(byte[] data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, myPublicKey);
ByteArrayInputStream input = new ByteArrayInputStream(data);
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[64];
int bytes;
ByteArrayOutputStream aux;
try {
while ((bytes = input.read(buffer)) != -1) {
aux = new ByteArrayOutputStream();
aux.write(buffer, 0, bytes);
byte[] fragment = aux.toByteArray();
byte[] encryptedFragment = cipher.doFinal(fragment);
output.write(encryptedFragment);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] result = output.toByteArray();
return result;
}
And this one for decryption:
public static String decrypt(byte[] data) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, IOException {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, myPrivateKey);
int bitLenght = ((java.security.interfaces.RSAPrivateKey) privateKey).getModulus().bitLength();
int blockSize = bitLenght / 8;
byte[] buffer = new byte[blockSize];
int bytes;
byte[] decrypted;
ByteArrayOutputStream aux;
ByteArrayInputStream input = new ByteArrayInputStream(data);
ByteArrayOutputStream output = new ByteArrayOutputStream();
while ((bytes = input.read(buffer)) != -1) {
aux = new ByteArrayOutputStream();
aux.write(buffer, 0, bytes);
byte[] fragment = aux.toByteArray();
byte[] decryptedFragment = cipher.doFinal(fragment);
output.write(decryptedFragment);
}
decrypted = output.toByteArray();
return new String(decrypted);
}
But I'm getting this exception:
javax.crypto.BadPaddingException: Decryption error
As I can see I've configured the Cipher to have the same PKCS1Padding so I can't guess why I'm getting that error.
I've created my private key as follows:
openssl genrsa -out myPrivateKey.key 2048
And the public one:
openssl rsa -in myPrivateKey.pem -pubout -out myPublicKey.key
As far as I can see with that command they are both PKCS1, in fact my private key starts with -----BEGIN RSA PRIVATE KEY-----.
What am I missing?
NOTE: I've also tried with blockSize = 64, same result.
Encrypting a stream - correctly you should have cipher.update(..) in the loop and .doFinal(..) called only once after processing all data.
When decrypting if you call doFinal on a partial message you may get the exception. Regardless that it is not apparent from your code if that is the issue you face. (assuming you have the keypair correcly imported)
And indeed RSA is intended only for short (117 bytes) messages. Otherwise you may search for "hybrid encryption"
P. S.: the way your process the streams and arrays is screaming for optimalization, so have a look at it too, but that is for different question
In my java program I would like to read a .txt file in and encode it afterwards. I know how to read a File in and tried to learn how to encode an array. The problem I have is that I don't know how to combine it, it doesn't work the way I tried it.
Here's the part I can read in my text file with:
public class ReadFile {
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("test.txt");
BufferedReader br = new BufferedReader(fr);
String zeile = "";
do
{
zeile = br.readLine();
System.out.println(zeile);
}
while (zeile != null);
br.close();
}
}
In this part I can encrypt and decrypt bytes:
public class Crypt {
public static void main(String[] args) {
try{
KeyGenerator keygenerator = KeyGenerator.getInstance("DES");
SecretKey myDesKey = keygenerator.generateKey();
Cipher desalgCipher;
desalgCipher = Cipher.getInstance("DES");
byte[] text = "test".getBytes("UTF8");
desalgCipher.init(Cipher.ENCRYPT_MODE, myDesKey);
byte[] textEncrypted = desalgCipher.doFinal(text);
String s = new String(textEncrypted);
System.out.println(s);
desalgCipher.init(Cipher.DECRYPT_MODE, myDesKey);
byte[] textDecrypted = desalgCipher.doFinal(textEncrypted);
s = new String(textDecrypted);
System.out.println(s);
}
catch(Exception e)
{
System.out.println("Error");
}
}
}
I thought to read the text file in and put it in a string to encode it, but I think it is way too complex. Is there another way to connect them, or is another way for encoding required?
I strongly advise you to use Streams ( see https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html & https://docs.oracle.com/javase/7/docs/api/java/io/OutputStream.html) rather than directly using a FileReader.
Encryption happens at a lower level (on bytes) than what you're trying to do.
Java ciphers offer the convenient CipherInputStream (and CipherOutputStream ) to encrypt byte streams on the fly. It's much cheaper and more scalable than trying to dump a whole file in a single byte[] (moreso because you're decoding and re-encoding the file content).
If you want an example of use, please look at the following snippet :
public static void encrypt(Path inputFile, OutputStream output) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException {
// init cipher
KeyGenerator keygenerator = KeyGenerator.getInstance("DES");
SecretKey myDesKey = keygenerator.generateKey();
Cipher desalgCipher;
desalgCipher = Cipher.getInstance("DES");
desalgCipher.init(Cipher.ENCRYPT_MODE, myDesKey);
try(InputStream is = Files.newInputStream(inputFile); // get an IS on your file
CipherInputStream cipherIS = new CipherInputStream(is, desalgCipher)){ // wraps input Stream with cipher
copyStreams(cipherIS, output); // copyStream is let to the implementer's choice.
}
}
And I'll let you figure out how to decrypt.
EDIT :
A common way to communicate encrypted bytes without fear for encoding issues is to encode the raw bytes with base 64.
You can wrap the outputStream with Base64.getEncoder().wrap(os)
FileReader/FileWriter are the wrong (old utility) classes, as they use the current platform encoding, and a file encrypted on one computer (Greek Windows) would not be decryptable on another computer (Linux server).
Text in java, String, is in Unicode. One cannot (should not) throw arbitrary bytes into a String.
So the following cannot be done
new String(textEncrypted); // Uses the default platform encoding
new String(textEncrypted, "UTF-8"); // Probably the bytes are not valid UTF-8
So do:
Path path = Paths.get("text.txt");
byte[] content = Files.readAllBytes(path);
content = encrypt(content);
Files.write(path, content);
I am using Android to encrypt and encrypt images sent between apps.
The encryption works well but when the file arrives at the destination it will not decrypt. Now I have copied the file at the destination app and decrypted it successfully using 3rd-party software.
The error I get is:"Error while finalizing cipher" at CipherInputStream (CipherInputStream.java:107) caused by IllegalBlockSizeException.
The encryption & decryption code is below:
public static String encrypt(String plainFile, String encryptedFile) throws IOException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException {
// Here you read the cleartext.
File extStore = Environment.getExternalStorageDirectory();
FileInputStream fis = new FileInputStream(plainFile);
// This stream write the encrypted text. This stream will be wrapped by
// another stream.
FileOutputStream fos = new FileOutputStream(encryptedFile);
// Length is 16 byte
SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(), "AES");
// Create cipher
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks);
// Wrap the output stream
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
// Write bytes
int b;
byte[] d = new byte[8];
while ((b = fis.read(d)) != -1) {
cos.write(d, 0, b);
}
// Flush and close streams.
cos.flush();
cos.close();
fis.close();
return encryptedFile;
}
static String decrypt(String plainFile, String encryptedFile) throws IOException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException {
File encFile=new File(encryptedFile);
FileInputStream fis = new FileInputStream(encFile);
FileOutputStream fos = new FileOutputStream(plainFile);
SecretKeySpec sks = new SecretKeySpec("MyDifficultPassw".getBytes(),
"AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks);
CipherInputStream cis = new CipherInputStream(fis, cipher);
int b;
byte[] d = new byte[8];
while ((b = cis.read(d)) != -1) {
fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();
return plainFile;
}
Any ideas? Thanks!
Ronan
Update:
The received encrypted file is consistently 1 byte smaller that the original file which seems to be generating the error. The error re block size is triggered at the code line
while ((b = fis.read(d)) != -1) { in the decrypt function.
Update:
Thanks for the feedback. The ultimate solution is as defined at last block incomplete with CipherInputStream/CipherOutputStream, even with padding AES/CBC/PKCS5Padding
Ronan
I am using some java code that encrypts the contents of a text file using Blowfish. When I convert the encrypted file back (i.e. decrypt it) the string is missing a character from the end. Any ideas why? I am very new to Java and have been fiddling with this for hours with no luck.
The file war_and_peace.txt just contains the string "This is some text". decrypted.txt contains "This is some tex" (with no t on the end). Here is the java code:
public static void encrypt(String key, InputStream is, OutputStream os) throws Throwable {
encryptOrDecrypt(key, Cipher.ENCRYPT_MODE, is, os);
}
public static void decrypt(String key, InputStream is, OutputStream os) throws Throwable {
encryptOrDecrypt(key, Cipher.DECRYPT_MODE, is, os);
}
private static byte[] getBytes(String toGet)
{
try
{
byte[] retVal = new byte[toGet.length()];
for (int i = 0; i < toGet.length(); i++)
{
char anychar = toGet.charAt(i);
retVal[i] = (byte)anychar;
}
return retVal;
}catch(Exception e)
{
String errorMsg = "ERROR: getBytes :" + e;
return null;
}
}
public static void encryptOrDecrypt(String key, int mode, InputStream is, OutputStream os) throws Throwable {
String iv = "12345678";
byte[] IVBytes = getBytes(iv);
IvParameterSpec IV = new IvParameterSpec(IVBytes);
byte[] KeyData = key.getBytes();
SecretKeySpec blowKey = new SecretKeySpec(KeyData, "Blowfish");
//Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/NoPadding");
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(Cipher.ENCRYPT_MODE, blowKey, IV);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
} else if (mode == Cipher.DECRYPT_MODE) {
cipher.init(Cipher.DECRYPT_MODE, blowKey, IV);
CipherOutputStream cos = new CipherOutputStream(os, cipher);
doCopy(is, cos);
}
}
public static void doCopy(InputStream is, OutputStream os) throws IOException {
byte[] bytes = new byte[4096];
//byte[] bytes = new byte[64];
int numBytes;
while ((numBytes = is.read(bytes)) != -1) {
os.write(bytes, 0, numBytes);
}
os.flush();
os.close();
is.close();
}
public static void main(String[] args) {
//Encrypt the reports
try {
String key = "squirrel123";
FileInputStream fis = new FileInputStream("war_and_peace.txt");
FileOutputStream fos = new FileOutputStream("encrypted.txt");
encrypt(key, fis, fos);
FileInputStream fis2 = new FileInputStream("encrypted.txt");
FileOutputStream fos2 = new FileOutputStream("decrypted.txt");
decrypt(key, fis2, fos2);
} catch (Throwable e) {
e.printStackTrace();
}
}
`
There is a couple of things not optimal here.
But let's first solve your problem. The reason why the last portion of your input is somehow missing is the padding you specify: none! Without specifying a padding, the Cipher can just operate on full-length blocks (8 bytes for Blowfish). Excess input that is less than a block long will be silently discarded, and there's your missing text. In detail: "This is some text" is 17 bytes long, so two full blocks will be decrypted, and the final 17th byte, "t", will be discarded.
Always use a padding in combination with symmetric block ciphers, PKCS5Padding is fine.
Next, when operating with Cipher, you don't need to implement your own getBytes() - there's String#getBytes already doing the job for you. Just be sure to operate on the same character encoding when getting the bytes and when reconstructing a String from bytes later on, it's a common source of errors.
You should have a look at the JCE docs, they will help you avoiding some of the common mistakes.
For example, using String keys directly is a no-go for symmetric cryptography, they do not contain enough entropy, which would make it easier to brute-force such a key. The JCE gives you theKeyGenerator class and you should always use it unless you know exactly what you are doing. It generates a securely random key of the appropriate size for you, but in addition, and that is something people tend to forget, it will also ensure that it doesn't create a weak key. For example, there are known weak keys for Blowfish that should be avoided in practical use.
Finally, you shouldn't use a deterministic IV when doing CBC encryption. There are some recent attacks that make it possible to exploit this, resulting in total recovery of the message, and that's obviously not cool. The IV should always be chosen at random (using a SecureRandom) in order to make it unpredictable. Cipher does this for you by default, you can simply obtain the used IV after encryption with Cipher#getIV.
On another note, less security-relevant: you should close streams in a finally block to ensure they're closed at all cost - otherwise you will be left with an open file handle in case of an exception.
Here's an updated version of your code that takes all these aspects into account (had to use Strings instead of files in main, but you can simply replace it with what you had there):
private static final String ALGORITHM = "Blowfish/CBC/PKCS5Padding";
/* now returns the IV that was used */
private static byte[] encrypt(SecretKey key,
InputStream is,
OutputStream os) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
return cipher.getIV();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static void decrypt(SecretKey key,
byte[] iv,
InputStream is,
OutputStream os)
{
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static void doCopy(InputStream is, OutputStream os)
throws IOException {
try {
byte[] bytes = new byte[4096];
int numBytes;
while ((numBytes = is.read(bytes)) != -1) {
os.write(bytes, 0, numBytes);
}
} finally {
is.close();
os.close();
}
}
public static void main(String[] args) {
try {
String plain = "I am very secret. Help!";
KeyGenerator keyGen = KeyGenerator.getInstance("Blowfish");
SecretKey key = keyGen.generateKey();
byte[] iv;
InputStream in = new ByteArrayInputStream(plain.getBytes("UTF-8"));
ByteArrayOutputStream out = new ByteArrayOutputStream();
iv = encrypt(key, in, out);
in = new ByteArrayInputStream(out.toByteArray());
out = new ByteArrayOutputStream();
decrypt(key, iv, in, out);
String result = new String(out.toByteArray(), "UTF-8");
System.out.println(result);
System.out.println(plain.equals(result)); // => true
} catch (Exception e) {
e.printStackTrace();
}
}
You have your CipherInputStream and CipherOutputStream mixed up. To encrypt, you read from a plain inputstream and write to a CipherOutputStream. To decrypt ... you get the idea.
EDIT:
What is happening is that you have specified NOPADDING and you are attempting to encrypt using a CipherInputStream. The first 16 bytes form two valid complete blocks and so are encrypted correctly. Then there is only 1 byte left over, and when the CipherInputStream class receives the end-of-file indication it performs a Cipher.doFinal() on the cipher object and receives an IllegalBlockSizeException. This exception is swallowed, and read returns -1 indicating end-of-file. If however you use PKCS5PADDING everything should work.
EDIT 2:
emboss is correct in that the real issue is simply that it is tricky and error-prone to use the CipherStream classes with the NOPADDING option. In fact, these classes explicitly state that they silently swallow every Security exception thrown by the underlying Cipher instance, so they are perhaps not a good choice for beginners.
Keys are binary, and String is not a container for binary data. Use a byte[].
When I had this problem I had to call doFinal on the cipher:
http://docs.oracle.com/javase/1.4.2/docs/api/javax/crypto/Cipher.html#doFinal()