Skip to content

Commit

Permalink
feat: Encyrption support for SevenZ
Browse files Browse the repository at this point in the history
Avoid incrasing the public API surface with uneccessary method

COMPRESS-633
  • Loading branch information
Dougniel committed Dec 9, 2022
1 parent 0f36b34 commit 2debc4e
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 48 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class AES256Options {
/**
* @param password password used for encryption
*/
public AES256Options(byte[] password) {
public AES256Options(char[] password) {
this(password, new byte[0], randomBytes(16), 19);
}

Expand All @@ -52,7 +52,7 @@ public AES256Options(byte[] password) {
* @param numCyclesPower another password security enforcer parameter that controls the cycles of password hashing. More the
* this number is hight, more security you'll have but also high CPU usage
*/
public AES256Options(byte[] password, byte[] salt, byte[] iv, int numCyclesPower) {
public AES256Options(char[] password, byte[] salt, byte[] iv, int numCyclesPower) {
this.salt = salt;
this.iv = iv;
this.numCyclesPower = numCyclesPower;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@
*/
package org.apache.commons.compress.archivers.sevenz;

import static java.nio.charset.StandardCharsets.UTF_16LE;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
Expand Down Expand Up @@ -179,6 +183,30 @@ public void close() throws IOException {
};
}

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

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

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

// remain bytes : salt/iv data
System.arraycopy(opts.getSalt(), 0, props, 2, opts.getSalt().length);
System.arraycopy(opts.getIv(), 0, props, 2 + opts.getSalt().length, opts.getIv().length);
}

return props;
}

static byte[] sha256Password(final char[] password, final int numCyclesPower, final byte[] salt) {
return sha256Password(utf16Decode(password), numCyclesPower, salt);
}

static byte[] sha256Password(final byte[] password, final int numCyclesPower, final byte[] salt) {
final MessageDigest digest;
try {
Expand All @@ -201,23 +229,23 @@ static byte[] sha256Password(final byte[] password, final int numCyclesPower, fi
return digest.digest();
}

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

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

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

// remain bytes : salt/iv data
System.arraycopy(opts.getSalt(), 0, props, 2, opts.getSalt().length);
System.arraycopy(opts.getIv(), 0, props, 2 + opts.getSalt().length, opts.getIv().length);
/**
* Convenience method that encodes Unicode characters into bytes in UTF-16 (ittle-endian byte order) charset
*
* @param chars characters to encode
* @return encoded characters
* @since 1.23
*/
static byte[] utf16Decode(final char[] chars) {
if (chars == null) {
return null;
}

return props;
final ByteBuffer encoded = UTF_16LE.encode(CharBuffer.wrap(chars));
if (encoded.hasArray()) {
return encoded.array();
}
final byte[] e = new byte[encoded.remaining()];
encoded.get(e);
return e;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package org.apache.commons.compress.archivers.sevenz;

import static java.nio.charset.StandardCharsets.UTF_16LE;
import static org.apache.commons.compress.utils.ByteUtils.utf16Decode;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
Expand Down Expand Up @@ -137,7 +136,7 @@ public SevenZFile(final File fileName, final char[] password) throws IOException
*/
public SevenZFile(final File fileName, final char[] password, final SevenZFileOptions options) throws IOException {
this(Files.newByteChannel(fileName.toPath(), EnumSet.of(StandardOpenOption.READ)), // NOSONAR
fileName.getAbsolutePath(), utf16Decode(password), true, options);
fileName.getAbsolutePath(), AES256SHA256Decoder.utf16Decode(password), true, options);
}

/**
Expand Down Expand Up @@ -256,7 +255,7 @@ public SevenZFile(final SeekableByteChannel channel, final String fileName,
*/
public SevenZFile(final SeekableByteChannel channel, final String fileName, final char[] password,
final SevenZFileOptions options) throws IOException {
this(channel, fileName, utf16Decode(password), false, options);
this(channel, fileName, AES256SHA256Decoder.utf16Decode(password), false, options);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package org.apache.commons.compress.archivers.sevenz;

import static java.nio.charset.StandardCharsets.UTF_16LE;
import static org.apache.commons.compress.utils.ByteUtils.utf16Decode;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
Expand Down Expand Up @@ -134,7 +133,7 @@ public SevenZOutputFile(final SeekableByteChannel channel, char[] password) thro
this.channel = channel;
channel.position(SevenZFile.SIGNATURE_HEADER_SIZE);
if (password != null) {
this.aes256Options = new AES256Options(utf16Decode(password));
this.aes256Options = new AES256Options(password);
}
}

Expand Down
24 changes: 0 additions & 24 deletions src/main/java/org/apache/commons/compress/utils/ByteUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,11 @@

package org.apache.commons.compress.utils;

import static java.nio.charset.StandardCharsets.UTF_16LE;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;

/**
* Utility methods for reading and writing bytes.
Expand Down Expand Up @@ -270,24 +266,4 @@ private static void checkReadLength(final int length) {
throw new IllegalArgumentException("Can't read more than eight bytes into a long value");
}
}

/**
* Convenience method that encodes Unicode characters into bytes in UTF-16 (ittle-endian byte order) charset
* @param chars characters to encode
* @return encoded characters
* @since 1.23
*/
public static byte[] utf16Decode(final char[] chars) {
if (chars == null) {
return null;
}
final ByteBuffer encoded = UTF_16LE.encode(CharBuffer.wrap(chars));
if (encoded.hasArray()) {
return encoded.array();
}
final byte[] e = new byte[encoded.remaining()];
encoded.get(e);
return e;
}
}

0 comments on commit 2debc4e

Please sign in to comment.