Cipher's doFinal() doesn't write bytes - java

This is my full code:
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Test {
public static void main(String[] args) throws Exception {
encrypt();
decrypt();
}
void encrypt() throws Exception {
Path file = Paths.get("path/to/file");
Path backupFile = file.getParent().resolve(file.getFileName().toString() + ".bak");
Files.deleteIfExists(backupFile);
Files.copy(file, backupFile);
SecureRandom secureRandom = new SecureRandom();
byte[] initializeVector = new byte[96 / Byte.SIZE];
secureRandom.nextBytes(initializeVector);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec p = new GCMParameterSpec(128, initializeVector);
try (FileChannel src = FileChannel.open(backupFile, READ);
FileChannel dest = FileChannel.open(file, WRITE, TRUNCATE_EXISTING)) {
SecretKeySpec secretKeySpec =
new SecretKeySpec(MessageDigest.getInstance("MD5").digest(new byte[]{0x00}), "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, p);
ByteBuffer ivBuffer = ByteBuffer.allocate(Integer.BYTES + cipher.getIV().length);
ivBuffer.putInt(cipher.getIV().length);
ivBuffer.put(cipher.getIV());
ivBuffer.flip();
dest.write(ivBuffer);
ByteBuffer readBuf = ByteBuffer.allocateDirect(8192);
ByteBuffer writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
while (src.read(readBuf) >= 0) {
if (cipher.getOutputSize(8192) > writeBuf.capacity()) {
writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
}
readBuf.flip();
cipher.update(readBuf, writeBuf);
writeBuf.flip();
dest.write(writeBuf);
readBuf.clear();
writeBuf.clear();
}
if (cipher.getOutputSize(0) > writeBuf.capacity()) {
writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(0));
}
cipher.doFinal(ByteBuffer.allocate(0), writeBuf);
writeBuf.flip();
dest.write(writeBuf);
Files.delete(backupFile);
} catch (ShortBufferException e) {
//Should not happen!
throw new RuntimeException(e);
}
}
void decrypt() throws Exception {
Path file = Paths.get("path/to/file");
Path backupFile = file.getParent().resolve(file.getFileName().toString() + ".bak");
Files.deleteIfExists(backupFile);
Files.copy(file, backupFile);
try (FileChannel src = FileChannel.open(backupFile, READ);
FileChannel dest = FileChannel.open(file, WRITE, TRUNCATE_EXISTING)) {
ByteBuffer ivLengthBuffer = ByteBuffer.allocate(Integer.BYTES);
src.read(ivLengthBuffer);
ivLengthBuffer.flip();
int ivLength = ivLengthBuffer.getInt();
ByteBuffer ivBuffer = ByteBuffer.allocate(ivLength);
src.read(ivBuffer);
ivBuffer.flip();
byte[] iv = new byte[ivBuffer.limit()];
ivBuffer.get(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec p = new GCMParameterSpec(128, iv);
SecretKeySpec secretKeySpec =
new SecretKeySpec(MessageDigest.getInstance("MD5").digest(new byte[]{0x00}), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, p);
ByteBuffer readBuf = ByteBuffer.allocateDirect(8192);
ByteBuffer writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
while (src.read(readBuf) >= 0) {
if (cipher.getOutputSize(8192) > writeBuf.capacity()) {
writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
}
readBuf.flip();
cipher.update(readBuf, writeBuf);
writeBuf.flip();
dest.write(writeBuf);
readBuf.clear();
writeBuf.clear();
}
if (cipher.getOutputSize(0) > writeBuf.capacity()) {
writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(0));
}
cipher.doFinal(ByteBuffer.allocate(0), writeBuf);
writeBuf.flip();
dest.write(writeBuf);
Files.deleteIfExists(backupFile);
}
}
}
I found a strange issue: if the original file (unencrypted) is bigger than 4KB, upon decrypting, cipher.update(readBuf, writeBuf) will write nothing to the buffer, cipher.doFinal(ByteBuffer.allocate(0), writeBuf) also write nothing, and finally I get my data lost. Every calling to cipher.getOutputSize(8192), increases the result, I don't know why it happen but it may help.
Why is it happening and how can I fix it?

.update() is easy; SunJCE implements the GCM (and CCM) requirement that authenticated decryption not release (any) plaintext if the authentication fails; see How come putting the GCM authentication tag at the end of a cipher stream require internal buffering during decryption? and https://moxie.org/blog/the-cryptographic-doom-principle/ . Because the tag is at the end of the ciphertext, this means it must buffer all the ciphertext until (one of the overloads of) doFinal() is called. (This is why for a large file your reallocation of writeBuf to cipher.getOutputSize(8192) keeps growing as you keep reading and buffering more data.)
.doFinal() is harder; it is supposed to work. However, I've narrowed down the failure: it only happens when you use ByteBuffers not raw byte[] arrays -- which is implemented in javax.crypto.CipherSpi.bufferCrypt rather than dispatching to the implementation class; and the output ByteBuffer has no backing array (i.e. was direct-allocated); and the plaintext is more than 4096 bytes. I'll try to look deeper into why this fails, but in the meantime changing either of the first two fixes it (or limiting your data to 4096 bytes, but presumably you don't want that).

Related

Java - decrypting messages using a private key [duplicate]

I'm writing a program which takes as input from the console - the name of a zip file, name of a zip file to be made containing the (de/en)crypted files generated from the first zip and a file containing the public key. I get the exception when decrypting:
exception Exception in thread "main" javax.crypto.BadPaddingException: Decryption error
at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:380)
at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:291)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:363)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.Main.decrypt(Main.java:67)
at com.Main.main(Main.java:201)
Can't figure out why I get this exception?
Public key:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCE3pA746UfpC8sFk8ZJp0yupyJqj5jy6cjdxUYoP7mCm7c0mqQDeCcDNBYW2eSozCioPrH/9L+CDQEPLYakoem+jFnUKDH5+pru/0PJTJJF8Xh/ZT9eJlvsYBr1/qSfICf6RTs7kzwq9IuSZBw7/tfNEF9i0A8FVox6HOopXod1QIDAQAB
Private key:
MIICXQIBAAKBgQCE3pA746UfpC8sFk8ZJp0yupyJqj5jy6cjdxUYoP7mCm7c0mqQDeCcDNBYW2eSozCioPrH/9L+CDQEPLYakoem+jFnUKDH5+pru/0PJTJJF8Xh/ZT9eJlvsYBr1/qSfICf6RTs7kzwq9IuSZBw7/tfNEF9i0A8FVox6HOopXod1QIDAQABAoGANOFrYBqK5lvu1koOswDWQZFZqcSSzh8IZyoGwGWa7S0r0EECXlDXmuPSq8e9IfRG8ALHrH+ZlrbnFOSgyVSWHfpj3aH+qknoSX5TW2rMQHih8865xuqheMQ+RTZ7+BRDqNsYkzxB/Z8mqzpoJQSYf+H7nWxdDCgAJVYZzxl3DmUCQQD32iEjnwiwUjii8slcmvCEZl+z84DWNdvJOg6Z38sI4AvrfpKc1WAcDg1rNZCKrRgokh54wpLt08cpFcrD04c3AkEAiTzDmc0bdgfg5wj6xHFZpYlBwiGm/bjOR2PS57P0GNU5PsDllRbFqIuzArITutO5lvZZImzuYz7Lf+cQ73pxUwJBAOdEwmdaneDo17A0m2+to3/nhqWDMVSwLMU3RyiNigZeCMFU+bkd4PBMrHi9IoJDwacZsRU9eZwxYEUV8H2Jg0ECQEEkOqRSm2pXKwX/WSjNtQPCNxhy6NUeV6vDUmTxIjh3XYjP/ynZeVEbnoj1BjB0N2/U11Jj6nPpZqb7gyppMEkCQQCoGdVYDipU+hMMnvxa0zOIyQc/a+HE0lESqn+2ZPafYi9Z1RldRMvUXhP8U7s+OuhRwprdw2ivvOFrnWyz9lL2
The code for the program is bellow . Any help is wellcomed :)
package com;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Enumeration;
import java.util.Scanner;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import javax.crypto.Cipher;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
public class Main {
public final static int BUFFER_SIZE = 117;
public static void decrypt(String originalZipFileName, String newZipFileName, String privateKeyFileName) throws Exception {
byte[] buffer = new byte[128];
ZipFile originalZipFile = new ZipFile(originalZipFileName);
ZipOutputStream newZipFile = new ZipOutputStream(new FileOutputStream(newZipFileName));
Enumeration<? extends ZipEntry> zipEntries = originalZipFile.entries();
String privateKey = getKeyString(privateKeyFileName);
PrivateKey key = makePrivateKey(privateKey);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, key);
while(zipEntries.hasMoreElements()){
ZipEntry entry = zipEntries.nextElement();
ZipEntry copy = new ZipEntry(entry.getName());
newZipFile.putNextEntry(copy);
InputStream inputEntry = originalZipFile.getInputStream(entry);
while(inputEntry.read(buffer) != -1){
newZipFile.write(cipher.doFinal(buffer));
}
newZipFile.closeEntry();
inputEntry.close();
}
newZipFile.close();
originalZipFile.close();
}
public static void encrypt(String originalZipFileName, String newZipFileName, String publicKeyFileName) throws Exception{
byte[] buffer = new byte[BUFFER_SIZE];
ZipFile originalZipFile = new ZipFile(originalZipFileName);
ZipOutputStream newZipFile = new ZipOutputStream(new FileOutputStream(newZipFileName));
Enumeration<? extends ZipEntry> zipEntries = originalZipFile.entries();
String publicKey = getKeyString(publicKeyFileName);
PublicKey key = makePublicKey(publicKey);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, key);
while(zipEntries.hasMoreElements()){
ZipEntry entry = zipEntries.nextElement();
ZipEntry copy = new ZipEntry(entry.getName());
newZipFile.putNextEntry(copy);
InputStream inputEntry = originalZipFile.getInputStream(entry);
while(inputEntry.read(buffer) != -1){
newZipFile.write(cipher.doFinal(buffer));
}
newZipFile.closeEntry();
inputEntry.close();
}
newZipFile.close();
originalZipFile.close();
}
public static String getKeyString(String fileName){
String key = new String();
try {
BufferedReader buf = new BufferedReader(new FileReader(fileName));
key = buf.readLine();
} catch ( IOException e) {
e.printStackTrace();
}
return key.trim();
}
public static PublicKey makePublicKey(String stored) throws GeneralSecurityException {
byte[] data = Base64.getDecoder().decode(stored);
X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
KeyFactory fact = KeyFactory.getInstance("RSA");
return fact.generatePublic(spec);
}
public static PrivateKey makePrivateKey(String stored) throws GeneralSecurityException, Exception {
/*byte[] data = Base64.getDecoder().decode(stored);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(data);
KeyFactory fact = KeyFactory.getInstance("RSA");
return fact.generatePrivate(spec);*/
byte[] data = Base64.getDecoder().decode(stored);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1Integer(0));
ASN1EncodableVector v2 = new ASN1EncodableVector();
v2.add(new ASN1ObjectIdentifier(PKCSObjectIdentifiers.rsaEncryption.getId()));
v2.add(DERNull.INSTANCE);
v.add(new DERSequence(v2));
v.add(new DEROctetString(data));
ASN1Sequence seq = new DERSequence(v);
byte[] privKey = seq.getEncoded("DER");
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privKey);
KeyFactory fact = KeyFactory.getInstance("RSA");
PrivateKey key = fact.generatePrivate(spec);
return key;
}
public static void main(String[] args) throws Exception {
Scanner scan = new Scanner(System.in);
System.out.println("Enter type of operation:");
String line = scan.nextLine();
if(line.equals("encrypt")){
System.out.println("Enter name of original ZIP file:");
String originalZipFileName = scan.nextLine();
System.out.println("Enter name of new ZIP file:");
String newZipFileName = scan.nextLine();
System.out.println("Enter name of file containg public key:");
String publicKeyFileName = scan.nextLine();
encrypt(originalZipFileName, newZipFileName, publicKeyFileName);
}
if(line.equals("decrypt")){
System.out.println("Enter name of original ZIP file:");
String originalZipFileName = scan.nextLine();
System.out.println("Enter name of new ZIP file:");
String newZipFileName = scan.nextLine();
System.out.println("Enter name of file containg private key:");
String privateKeyFileName = scan.nextLine();
decrypt(originalZipFileName, newZipFileName, privateKeyFileName);
}
}
}
PS: Updated decrypt method. Still gives same error.
public static void decrypt(String originalZipFileName, String newZipFileName, String privateKeyFileName) throws Exception {
byte[] buffer = new byte[128];
ZipFile originalZipFile = new ZipFile(originalZipFileName);
ZipOutputStream newZipFile = new ZipOutputStream(new FileOutputStream(newZipFileName));
Enumeration<? extends ZipEntry> zipEntries = originalZipFile.entries();
String privateKey = getKeyString(privateKeyFileName);
PrivateKey key = makePrivateKey(privateKey);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, key);
while(zipEntries.hasMoreElements()){
ZipEntry entry = zipEntries.nextElement();
ZipEntry copy = new ZipEntry(entry.getName());
newZipFile.putNextEntry(copy);
InputStream inputEntry = originalZipFile.getInputStream(entry);
while(inputEntry.read(buffer) != -1){
newZipFile.write(cipher.doFinal(buffer));
}
newZipFile.closeEntry();
inputEntry.close();
}
newZipFile.close();
originalZipFile.close();
}
Jozef is right.
When you create cipher with default parameters, it defaults to "RSA/ECB/PKCS1Padding". You should specify padding explicitly, if you don't like nasty surprises. Because other security providers might have different default parameters. And you never know in advance which security settings each specific JRE has.
So PKCS1 padding adds 11 bytes to your original data increasing it from 117 bytes to 128 bytes. You should take into account that these numbers are specific to 1024 bit RSA keys (which are marginally secure) and will be different for longer keys. Since you are loading the key from a file consider checking its length.
#Test
public void testPadding() throws Exception {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024, random);
KeyPair keyPair = keyGen.generateKeyPair();
/* constant 117 is a public key size - 11 */
byte[] plaintext = new byte[117];
random.nextBytes(plaintext);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
byte[] ciphertext = cipher.doFinal(plaintext);
System.out.println(plaintext.length + " becomes " + ciphertext.length);
}
This prints
117 becomes 128
And finally, consider using AES instead of RSA for file encryption.
So to fix the problem you need to use buffer of size public key length - 11 (117) for encryption and public key size (128) for decryption.
Change
outputFile.write(cipher.doFinal(buffer), 0, read);
to
outputFile.write(cipher.doFinal(buffer));
because buffer read is 117 bytes and size of doFinal result is 128 bytes.
Also you need to buffer input streams. When you are reading from file, it can be slow sometimes and then InputStream will read less data than buffer may contain. By using BufferedInputStream one ensures that there is enough data before read call returns. However, for decryption it's crucial to have the full block of data
InputStream inputEntry = new BufferedInputStream(originalZipFile.getInputStream(entry));
while((read = inputEntry.read(buffer)) != -1){
outputFile.write(cipher.doFinal(buffer), 0, read);
}
You have a problem here. read is the size of the plaintext that was read, not the ciphertext. You should remove the 2nd and 3rd parameters altogether.
It is also a waste of time and space to write the ciphertext to an intermediate file. Just write it straight to the zip stream.
The decrypt method's byte array should be 256 bytes in length as it is the default output size of the algorithm (The extra bytes result in this length). Change byte[] buffer = new byte[128]; to byte[] buffer = new byte[256];.

Java AES Padding Encryption Error While Transferring Video over TCP

I am currently doing a project in Java to transfer a video file over TCP from a server to a client. The idea is the server will continue running and listen to incoming connection. Once there is an incoming connection from a client, the server will automatically send a video file to the client. (As of now the IP and file name is hard coded). The idea is so that the file can be copied and played at the same time
It is used locally and will automatically turn on VLC from the receiving computer to play the file being transferred. I have done the transfer part with no issues. The only problem comes up when I try to encrypt/decrypt the file. The code I have are below
Runnable Thread FileTransfer Server
public class FileTransferServer {
public static void main(String[] args) throws Exception {
//Initialize Sockets
int i = 0;
ServerSocket ssock = new ServerSocket(6012);
while (true){
ClientConnection CC;
CC = new ClientConnection(ssock.accept());
Thread t = new Thread(CC);
t.start();
}
}
}
Server Java File
import java.io.BufferedInputStream;
import javax.crypto.Cipher;
import java.io.InputStream;
import java.io.OutputStream;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class ClientConnection implements Runnable
{
private Socket socketPort;
public ClientConnection (Socket socketPort)
{
this.socketPort = socketPort;
}
public void run()
{
try {
DataInputStream input = new DataInputStream(socketPort.getInputStream());
String videoName = input.readUTF();
// automatically get local ip
InetAddress IA = InetAddress.getByName("10.0.0.1");
String key = "Maryhadonecat111";
byte[] keyByte = key.getBytes("UTF-8");
System.out.println(keyByte);
System.out.println(keyByte.toString());
//Specify the file
File file = new File("D:\\Temp\\"+videoName);
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
//Get socket's output stream
OutputStream os = socketPort.getOutputStream();
//Read File Contents into contents array
byte[] contents;
long fileLength = file.length();
long current = 0;
long start = System.nanoTime();
while(current!=fileLength){
int size = 1000000;
if(fileLength - current >= size)
current += size;
else{
size = (int)(fileLength - current);
current = fileLength;
}
contents = new byte[size];
bis.read(contents, 0, size);
//os.write(contents);
os.write(CryptoTest1.doEncrypt(contents,keyByte));
System.out.print("Sending file to "+ socketPort.getInetAddress().toString() +" " +(current*100)/fileLength+"% complete!\n");
}
os.flush();
//File transfer done. Close the socket connection!
socketPort.close();
// ssock.close();
System.out.println("File sent succesfully!");
} catch (Exception e)
{
System.out.println(e);
}
}
}
Client Java File
import java.io.BufferedOutputStream;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import javax.crypto.Cipher;
import java.io.InputStream;
import java.io.OutputStream;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
public class FileTransferClient {
public static void main(String[] args) throws Exception{
requestFile("10.0.0.1", "papa.avi");
}
public static void requestFile(String IP, String videoName) throws Exception{
//Initialize socket
Socket socket = new Socket(InetAddress.getByName(IP), 6012);
DataOutputStream output = new DataOutputStream( socket.getOutputStream());
output.writeUTF(videoName);
String key = "Maryhadonecat111";
byte[] keyByte = key.getBytes("UTF-8");
byte[] contents = new byte[1000000];
//Initialize the FileOutputStream to the output file's full path.
FileOutputStream fos = new FileOutputStream("D:\\Temp2\\"+videoName);
BufferedOutputStream bos = new BufferedOutputStream(fos);
InputStream is = socket.getInputStream();
System.out.println("Receiving File");
ProcessBuilder pb = new ProcessBuilder("C:\\Program Files (x86)\\VideoLAN\\VLC\\vlc.exe", "D:\\Temp2\\"+videoName);
Process start = pb.start();
//No of bytes read in one read() call
int bytesRead = 0;
while((bytesRead=is.read(contents))!=-1){
System.out.println("Bytes Received: " + bytesRead);
contents = (CryptoTest1.doDecrypt(contents,keyByte));
bos.write(contents, 0, bytesRead);
}
bos.flush();
socket.close();
System.out.println("File saved successfully!");
}
}
CryptoTest1 Java File
public class CryptoTest1
{
public static byte[] doEncrypt(byte[] msg, byte[] key) throws Exception {
//prepare key
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
//prepare cipher
String cipherALG = "AES/CBC/PKCS5Padding"; // use your preferred algorithm
Cipher cipher = Cipher.getInstance(cipherALG);
String string = cipher.getAlgorithm();
//as iv (Initial Vector) is only required for CBC mode
if (string.contains("CBC")) {
//IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
IvParameterSpec ivParameterSpec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
} else {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
}
byte[] encMessage = cipher.doFinal(msg);
return encMessage;
}
public static byte[] doDecrypt(byte[] encMsgtoDec, byte[] key) throws Exception {
//prepare key
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
//prepare cipher
String cipherALG = "AES/CBC/PKCS5Padding"; // use your preferred algorithm
Cipher cipher = Cipher.getInstance(cipherALG);
String string = cipher.getAlgorithm();
//as iv (Initial Vector) is only required for CBC mode
if (string.contains("CBC")) {
//IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
IvParameterSpec ivParameterSpec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
} else {
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
}
byte[] decMsg = cipher.doFinal(encMsgtoDec);
return decMsg;
}
}
Issue
I seem to have no issue encrypting the file and it is being sent over. The issue is decrypting the file. I can't seem to get it working. I've tried a lot of times and most of the errors comes down to "Padding Exception"
I am currently using AES/CBC/PKCS5Padding but I have tried the following
AES/CBC/PKCS5Padding
AES/CBC/NoPadding
AES/ECB/PKCS5Padding
AES/ECB/NoPadding
If I use padding, i will get an exception
javax.crypto.BadPaddingException: Given final block not properly padded
If I do not use padding, I will get an exception of
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes.
Some other exception I have encountered while I was tinkering
Missing Parameter
java.security.InvalidKeyException: Illegal key size
java.security.InvalidKeyException: Invalid AES key length: 64 bytes
I would like to ask if anyone of you would be willing to point me in a proper direction as to what I am doing wrong. I am still very new to Java, so please assume that I have very little knowledge.
I have searched Stackoverflow for a long time and most encryption questions here are on text files, not on an actual video. If the encryption method I am using is not suitable for video, please let me know if there is a better one.
The IV must be the same for both encryption and decryption. Generally a random IV is created on encryption and this must be provided to the decryption method. One way to handle this is to prefix the encrypted data with with the IV so on decryption it will be available.
Notes:
AES is a block cipher so the input must be an exact multiple of the block size, this is generally accomplished with padding.
A bad padding error generally means that either the key, IV or encrypted data is incorrect, not that the padding is incorrect. The padding is incorrect because the decryption failed.

Spring Security Crypto final block not properly padded

I'm trying to create a proof of concept of file encryption with spring security crypto. Per some of the other posts I've found I have downloaded the Unlimited JCE Policy and added the jars to my java_home/jre/lib/security folder.
The error occurs when calling bytesEncryptor.decrypt. The encrypt may be working as there is encrypted content in the file after execution but I'm not sure of a way to confirm that's not where the problem lies. Some other posts about this error that aren't using Spring are saying the key is not right but this can't be because I'm using the same bytesEncryptor object? (Given final block not properly padded)
Stack:
Exception in thread "main" java.lang.IllegalStateException: Unable to invoke Cipher due to bad padding
at org.springframework.security.crypto.encrypt.CipherUtils.doFinal(CipherUtils.java:142)
at org.springframework.security.crypto.encrypt.AesBytesEncryptor.decrypt(AesBytesEncryptor.java:128)
at com.test.encryption.MyTest.crypt(MyTest.java:45)
at com.test.encryption.MyTest.decryptFile(MyTest.java:31)
at com.test.encryption.MyTest.main(MyTest.java:21)
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at org.springframework.security.crypto.encrypt.CipherUtils.doFinal(CipherUtils.java:135)
... 4 more
Code:
public static void main(String args[]) throws Exception {
String salt = KeyGenerators.string().generateKey();
BytesEncryptor bytesEncryptor = Encryptors.standard("password", salt);
encryptFile(bytesEncryptor, "C:/test/testIn.txt", "C:/test/testOut.txt");
decryptFile(bytesEncryptor, "C:/test/testOut.txt", "C:/test/testOutDecode.txt");
}
private static void encryptFile (BytesEncryptor bytesEncryptor, String in, String out) throws Exception {
crypt(bytesEncryptor, in, out, true);
}
private static void decryptFile (BytesEncryptor bytesEncryptor, String in, String out) throws Exception {
crypt(bytesEncryptor, in, out, false);
}
private static void crypt (BytesEncryptor bytesEncryptor, String in, String out, boolean encrypt) throws Exception {
byte[] buffer = new byte[1024];
int numRead;
byte[] bytes = null;
InputStream input = new FileInputStream(in);
OutputStream output = new FileOutputStream(out);
while ((numRead = input.read(buffer)) > 0) {
if(encrypt) {
bytes = bytesEncryptor.encrypt(buffer);
} else {
bytes = bytesEncryptor.decrypt(buffer);
}
if (bytes != null) {
output.write(bytes, 0, numRead);
}
}
input.close();
output.close();
}
For what it's worth, I got this message, when I was using the wrong password/salt to decrypt (same length though)
I suspect the problem comes from they way you read your file in chunks of 1024 bytes. AES is a block cipher so operates on blocks of data of a certain size. When it enciphers it will pad output to make sure it fits into chunks of the appropriate size. When you give it data to decipher it expects the data to be similarly padded if necessary.
Try reading the whole file into a byte array, then encrypting and decrypting that. Alternatively you could try and use a chunk size matching the block size you are using for AES (it will be 128, 192 or 256 bits).
Turns out you need to use a CipherInputStream and CipherOutputStream for FileInputStreams and FileOutputStreams.
Below is more or less taken from this http://www.programcreek.com/java-api-examples/index.php?source_dir=cube-master/cube-common/src/main/java/ch/admin/vbs/cube/common/crypto/AESEncrypter.java
Updated code:
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
public class MyTest {
private static String algorithm = "AES/CBC/PKCS5Padding";
public static void main(String args[]) throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
final int AES_KEYLENGTH = 256;
byte[] iv = new byte[AES_KEYLENGTH / 16];
SecureRandom prng = new SecureRandom();
prng.nextBytes(iv);
Cipher aesCipherForEncryption = Cipher.getInstance(algorithm);
Cipher aesCipherForDecryption = Cipher.getInstance(algorithm);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
encryptFile(aesCipherForEncryption, "C:/test/testIn.txt", "C:/test/testOut.txt");
decryptFile(aesCipherForDecryption, "C:/test/testOut.txt", "C:/test/testOutDecode.txt");
}
private static void encryptFile (Cipher cipher, String in, String out) throws Exception {
crypt(cipher, in, out, true);
}
private static void decryptFile (Cipher cipher, String in, String out) throws Exception {
crypt(cipher, in, out, false);
}
private static void crypt (Cipher cipher, String in, String out, boolean encrypt) throws Exception {
byte[] buffer = new byte[256];
int numRead;
InputStream input = new FileInputStream(in);
OutputStream output = new FileOutputStream(out);
if(encrypt) {
output = new CipherOutputStream(output, cipher);
} else {
input = new CipherInputStream(input, cipher);
}
while ((numRead = input.read(buffer)) >= 0) {
output.write(buffer, 0, numRead);
}
input.close();
output.close();
}
}
Note - I did end up going away from using the Spring Security Crypto implementation.

Cannot find symbol Java Initilisation RSA

Hi I would like to run the following command
// Initialise the RSA cipher with PUBLIC key
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
But I have the following error that I cant find any symbol in Cipher cipher = cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
Here are the following packages I imported:
import java.net.*;
import java.io.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import sun.misc.*;
import java.io.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.lang.Exception;
Here is the full code that acts as a server which receives incoming connection. Upon accepting connections, it will generate an AES key and a RSA Cipher, which will be used to encrypt the key
import java.net.*;
import java.io.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import sun.misc.*;
import java.io.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.lang.Exception;
public class Server {
public static void main (String [] args ) throws IOException {
ServerSocket serverSocket = new ServerSocket(15123);
Socket socket = serverSocket.accept();
System.out.println("Accepted connection : " + socket);
System.out.println("A Simple Program using RSA to encrypt a single symmetric key using");
System.out.println("Advanced Encryption Standard (AES).\n");
System.out.println("Generating a symmetric (AES) key...");
try{
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
Key AESKey = keyGenerator.generateKey();
System.out.println("Format: "+AESKey.getFormat());
} catch (Exception e){
System.out.println("WRONG!");
}
// Create an RSA key pair
System.out.println("Generating an RSA key...");
try{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
// Insert your statement here
KeyPair keyPair = keyPairGenerator.genKeyPair();
System.out.println(keyPair.getPublic().getFormat());
System.out.println(keyPair.getPrivate().getFormat());
String publicKeyFilename = "public";
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
X509EncodedKeySpec x509Encoded = new X509EncodedKeySpec(publicKeyBytes);
FileOutputStream fos = new FileOutputStream(publicKeyFilename);
fos.write(x509Encoded.getEncoded());
fos.close();
String privateKeyFilename = "privateKeyFilename";
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
//byte[] encryptedPrivateKeyBytes = passwordEncrypt(password.toCharArray(), privateKeyBytes);
PKCS8EncodedKeySpec pkcs8Encoded = new PKCS8EncodedKeySpec(privateKeyBytes);
fos = new FileOutputStream(privateKeyFilename);
fos.write(pkcs8Encoded.getEncoded());
fos.close();
System.out.println("Done generating the key.\n");
} catch (Exception e){
System.out.println("WRONG!");
}
// Initialise the RSA cipher with PUBLIC key
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// Insert your statement here
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
// Get the bytes of the AES key
byte[] encryptedKey = AESKey.getEncoded();
System.out.println("AES key:\n" + asHex(encryptedKey) + "\n");
// Perform the actual encryption on those bytes
byte[] cipherText = cipher.doFinal(encryptedKey);
System.out.println("Encrypted key:\n" + asHex(cipherText) + "\n");
File transferFile = new File ("copy.doc");
byte [] bytearray = new byte [(int)transferFile.length()];
FileInputStream fin = new FileInputStream(transferFile);
BufferedInputStream bin = new BufferedInputStream(fin);
bin.read(bytearray,0,bytearray.length);
OutputStream os = socket.getOutputStream();
System.out.println("Sending Files...");
os.write(bytearray,0,bytearray.length);
os.flush();
socket.close();
System.out.println("File transfer complete");
}
public static String asHex (byte buf[]) {
//Obtain a StringBuffer object
StringBuffer strbuf = new StringBuffer(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++) {
if (((int) buf[i] & 0xff) < 0x10)
strbuf.append("0");
strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
}
// Return result string in Hexadecimal format
return strbuf.toString();
}
}
Please help!
The declaration of the variable keyPair is done in a try block. So the scope of the variable keyPair is this block. cipher.init is called outside the block, so the variable keypPair does not exists in this block.
Solution is before your try block, add:
KeyPair keypair = null;
and then just associate the value in your try block
The answer of seneque is correct. The answer is indeed to declare the varialbe before the try / catch. There is however no need to initialize it with null. Instead, just make your error handling more specific:
final KeyPair keyPair;
try {
...
keyPair = keyPairGenerator.genKeyPair();
...
} catch(final Exception e) {
throw new RuntimeException("Exception during key pair generation not handled", e);
}
...
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
...
Now because you are throwing a RuntimeException the compiler knows the init statement is never reached and hence the keyPair variable doesn't need to get assigned a value. If there is no exception then the keyPair is now both declared and initialized you won't get the compile time error anymore.
Of course, in your final code you want to work out the exact exceptions you want to catch and how to handle them. At that time you should handle exceptions based on program state and programming errors/runtime errors differently.
Anything declared between { and } becomes out-of-scope after the }. Not much used, but you can even just use { and } (without an if or while) to define a new scope. This is useful in switch/case constructs or during testing because you may get name clashes if you don't introduce a new scope.

how can the decryption happen at a later stage with this AES program

i am really not able to figure this out myself so please help me out.
this is a program to encrypt contents of an existing file keeper.txt using 128bit AES putting the encrypted text into newly created file called Encrypted.txt, then decrypting the contents of Encrypted.txt into newly created file called Decrypted.txt
everytime this program is run it generates a random key for the encryption.
i am just trying to figure out if i have to give somebody the Encrypted.txt file and how can he decrypt the file later using this code or by slightly modifying this code .
i think its not possible to send him the key generated by this program .. is it ?
cause when i try to print the key using system.out it doesn't give the key.
help me out please
package org.temp2.cod1;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import java.security.spec.AlgorithmParameterSpec;
public class AESEncrypter
{
Cipher ecipher;
Cipher dcipher;
public AESEncrypter(SecretKey key)
{
// Create an 8-byte initialization vector
byte[] iv = new byte[]
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
};
AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
try
{
ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// CBC requires an initialization vector
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
}
catch (Exception e)
{
e.printStackTrace();
}
}
// Buffer used to transport the bytes from one stream to another
byte[] buf = new byte[1024];
public void encrypt(InputStream in, OutputStream out)
{
try
{
// Bytes written to out will be encrypted
out = new CipherOutputStream(out, ecipher);
// Read in the cleartext bytes and write to out to encrypt
int numRead = 0;
while ((numRead = in.read(buf)) >= 0)
{
out.write(buf, 0, numRead);
}
out.close();
}
catch (java.io.IOException e)
{
}
}
public void decrypt(InputStream in, OutputStream out)
{
try
{
// Bytes read from in will be decrypted
in = new CipherInputStream(in, dcipher);
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = in.read(buf)) >= 0)
{
out.write(buf, 0, numRead);
}
out.close();
}
catch (java.io.IOException e)
{
}
}
public static void main(String args[])
{
try
{
// Generate a temporary key. In practice, you would save this key.
// See also e464 Encrypting with DES Using a Pass Phrase.
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey key = kgen.generateKey();
// Create encrypter/decrypter class
AESEncrypter encrypter = new AESEncrypter(key);
// Encrypt
encrypter.encrypt(new FileInputStream("C:\\keeper.txt"),new FileOutputStream("C:\\Encrypted.txt"));
// Decrypt
encrypter.decrypt(new FileInputStream("C:\\Encrypted.txt"),new FileOutputStream("C:\\Decrypted.txt"));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
You can get encoded version of the key by calling
byte[] keyBytes = secretKey.getEncoded();
For AES, there is not really any encoding involved so you will get the raw bytes (16 bytes for 128-bit). The keyBytes is binary so you can't print it. You can hex or base64 encode it if you need to send it in text format. The other party can reconstitute the key like this,
SecretKey key = new SecretKeySpec(keyBytes, "AES");
I answered another your question yesterday regarding Sun's AES example. This is where the author was confused. He did both steps at the same time.
Often, it's easier for human to remember if you use password as the secret and generate the key from password. This is called PBE (Password-Based Encryption). Following is the code I use to generate keys,
public static SecretKey getAesKey(char[] password, int keyLength)
throws GeneralSecurityException {
int count = 128; // Iteration count
byte[] salt;
try {
salt = "This is a fixed salt string".getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("No UTF-8");
}
PBEKeySpec keySpec = new PBEKeySpec(password, salt, count, keyLength);
SecretKeyFactory skf = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
SecretKey pbeKey = skf.generateSecret(keySpec);
byte[] raw = pbeKey.getEncoded();
return new SecretKeySpec(raw, "AES");
}
One more recommendation to your code, it's much more secure if you use a different random IV every time. IV doesn't need to be secret so you can prepend it to your ciphertext.
Try getting the key by calling key.getEncoded(). This would return a byte array. Then you can use a Base64 utility (like apache-commons codec) to transform the byte array into String.
Be advised, however, that sending someone the encrypted file as well as the decryption key is a compromised action from security perspective.

Categories