Skip to content

Commit

Permalink
feat(encrypt): padding corrption
Browse files Browse the repository at this point in the history
When the size of file is < cipher block size (16 bytes)

#1
  • Loading branch information
Dougniel committed Nov 29, 2022
1 parent 4b8c3e3 commit ed60a16
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,25 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.compress.PasswordRequiredException;

class AES256SHA256Coder extends CoderBase {
class AES256SHA256Decoder extends CoderBase {

AES256SHA256Coder() {
AES256SHA256Decoder() {
super(AES256Options.class);
}

@Override
InputStream decode(
final String archiveName,
final InputStream in,
final long uncompressedLength,
final Coder coder,
final byte[] passwordBytes,
final int maxMemoryLimitInKb
) {
InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength,
final Coder coder, final byte[] passwordBytes, final int maxMemoryLimitInKb) {
return new InputStream() {
private boolean isInitialized;
private CipherInputStream cipherInputStream;
Expand Down Expand Up @@ -81,37 +77,12 @@ private CipherInputStream init() throws IOException {
if (numCyclesPower == 0x3f) {
aesKeyBytes = new byte[32];
System.arraycopy(salt, 0, aesKeyBytes, 0, saltSize);
System.arraycopy(
passwordBytes,
0,
aesKeyBytes,
saltSize,
Math.min(passwordBytes.length, aesKeyBytes.length - saltSize)
);
System.arraycopy(passwordBytes, 0, aesKeyBytes, saltSize,
Math.min(passwordBytes.length, aesKeyBytes.length - saltSize));
} else {
final MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (final NoSuchAlgorithmException noSuchAlgorithmException) {
throw new IOException("SHA-256 is unsupported by your Java implementation", noSuchAlgorithmException);
}
final byte[] extra = new byte[8];
for (long j = 0; j < (1L << numCyclesPower); j++) {
digest.update(salt);
digest.update(passwordBytes);
digest.update(extra);
for (int k = 0; k < extra.length; k++) {
++extra[k];
if (extra[k] != 0) {
break;
}
}
}
aesKeyBytes = digest.digest();
aesKeyBytes = sha256Password(passwordBytes, numCyclesPower, salt);
}

System.out.println(aesKeyBytes);

final SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
try {
final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
Expand All @@ -120,11 +91,10 @@ private CipherInputStream init() throws IOException {
isInitialized = true;
return cipherInputStream;
} catch (final GeneralSecurityException generalSecurityException) {
throw new IOException(
"Decryption error " + "(do you have the JCE Unlimited Strength Jurisdiction Policy Files installed?)",
generalSecurityException
);
}
throw new IOException("Decryption error " +
"(do you have the JCE Unlimited Strength Jurisdiction Policy Files installed?)",
generalSecurityException);
}
}

@Override
Expand All @@ -148,87 +118,114 @@ public void close() throws IOException {

@Override
OutputStream encode(OutputStream out, Object options) throws IOException {
AES256Options opts = (AES256Options) options;
final byte[] aesKeyBytes = sha256Password(opts.password, opts.numCyclesPower, opts.salt);
final SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");

final Cipher cipher;
try {
cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(opts.iv));
} catch (final GeneralSecurityException generalSecurityException) {
throw new IOException(
"Encryption error " + "(do you have the JCE Unlimited Strength Jurisdiction Policy Files installed?)",
generalSecurityException
);
}
return new OutputStream() {
private boolean isInitialized;
private CipherOutputStream cipherOutputStream;
final CipherOutputStream cipherOutputStream = new CipherOutputStream(out, cipher);

private CipherOutputStream init() throws IOException {
if (isInitialized) {
return cipherOutputStream;
}

AES256Options opts = (AES256Options) options;
// Ensures that data are encrypt in respect of cipher block size and pad with '0' if smaller
// NOTE: As "AES/CBC/PKCS5Padding" is weak and should not be used, we use "AES/CBC/NoPadding" with this
// manual implementation for padding possible thanks to the size of the file stored separately
final int cipherBlockSize = cipher.getBlockSize();
final byte[] cipherBlockBuffer = new byte[cipherBlockSize];
private int count = 0;

final MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (final NoSuchAlgorithmException noSuchAlgorithmException) {
throw new IOException("SHA-256 is unsupported by your Java implementation", noSuchAlgorithmException);
}
final byte[] extra = new byte[8];
for (long j = 0; j < (1L << opts.numCyclesPower); j++) {
digest.update(opts.salt);
digest.update(opts.password);
digest.update(extra);
for (int k = 0; k < extra.length; k++) {
++extra[k];
if (extra[k] != 0) {
break;
}
}
}
final byte[] aesKeyBytes = digest.digest();
System.out.println(aesKeyBytes);

final SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
try {
final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(opts.iv));
cipherOutputStream = new CipherOutputStream(out, cipher);
isInitialized = true;
return cipherOutputStream;
} catch (final GeneralSecurityException generalSecurityException) {
throw new IOException(
"Decryption error " + "(do you have the JCE Unlimited Strength Jurisdiction Policy Files installed?)",
generalSecurityException
);
@Override
public void write(int b) throws IOException {
cipherBlockBuffer[count++] = (byte) b;
if (count == cipherBlockSize) {
flushBuffer();
}
}

@Override
public void write(int b) throws IOException {
init().write(b);
public void write(byte[] b, int off, int len) throws IOException {
int gap = len + count > cipherBlockSize ? cipherBlockSize - count : len;
System.arraycopy(b, off, cipherBlockBuffer, count, gap);
count += gap;

if (count == cipherBlockSize) {
flushBuffer();

if (len - gap >= cipherBlockSize) {
// skip buffer to encrypt data chunks big enought to fit cipher block size
int multipleCipherBlockSizeLen = (len - gap) / cipherBlockSize * cipherBlockSize;
cipherOutputStream.write(b, off + gap, multipleCipherBlockSizeLen);
gap += multipleCipherBlockSizeLen;
}
System.arraycopy(b, off + gap, cipherBlockBuffer, 0, len - gap);
count = len - gap;
}
}

@Override
public void write(byte[] b) throws IOException {
init().write(b);
private void flushBuffer() throws IOException {
cipherOutputStream.write(cipherBlockBuffer);
count = 0;
Arrays.fill(cipherBlockBuffer, (byte) 0);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
init().write(b, off, len);
public void flush() throws IOException {
cipherOutputStream.flush();
}

@Override
public void close() throws IOException {
if (cipherOutputStream != null) {
cipherOutputStream.close();
if (count > 0) {
cipherOutputStream.write(cipherBlockBuffer);
}
cipherOutputStream.close();
}
};
}

private byte[] sha256Password(final byte[] password, final int numCyclesPower, final byte[] salt) throws IOException {
final MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (final NoSuchAlgorithmException noSuchAlgorithmException) {
throw new IOException("SHA-256 is unsupported by your Java implementation", noSuchAlgorithmException);
}
final byte[] extra = new byte[8];
for (long j = 0; j < (1L << numCyclesPower); j++) {
digest.update(salt);
digest.update(password);
digest.update(extra);
for (int k = 0; k < extra.length; k++) {
++extra[k];
if (extra[k] != 0) {
break;
}
}
}
return digest.digest();
}

@Override
byte[] getOptionsAsProperties(Object options) throws IOException {
AES256Options opts = (AES256Options) options;
byte[] props = new byte[2 + opts.salt.length + opts.iv.length];

// First byte : control (numCyclesPower + flags of salt or iv presence)
props[0] = (byte) (opts.numCyclesPower | (opts.salt.length == 0 ? 0 : (1 << 7)) | (opts.iv.length == 0 ? 0 : (1 << 6)));

if (opts.salt.length != 0 || opts.iv.length != 0) {
// second byte : size of salt/iv data
props[1] = (byte) (((opts.salt.length == 0 ? 0 : opts.salt.length - 1) << 4) | (opts.iv.length == 0 ? 0 : opts.iv.length - 1));

// remain bytes : salt/iv data
System.arraycopy(opts.salt, 0, props, 2, opts.salt.length);
System.arraycopy(opts.iv, 0, props, 2 + opts.salt.length, opts.iv.length);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Coders {
put(SevenZMethod.LZMA, new LZMADecoder());
put(SevenZMethod.LZMA2, new LZMA2Decoder());
put(SevenZMethod.DEFLATE, new DeflateDecoder());
put(SevenZMethod.AES256SHA256, new AES256SHA256Coder());
put(SevenZMethod.AES256SHA256, new AES256SHA256Decoder());
put(SevenZMethod.BCJ_X86_FILTER, new BCJDecoder(new X86Options()));
put(SevenZMethod.BCJ_PPC_FILTER, new BCJDecoder(new PowerPCOptions()));
put(SevenZMethod.BCJ_IA64_FILTER, new BCJDecoder(new IA64Options()));
Expand Down
Loading

0 comments on commit ed60a16

Please sign in to comment.