From 2debc4e4d58456e859bbd38eafa0a3d9b2691896 Mon Sep 17 00:00:00 2001 From: Daniel Santos Date: Sun, 4 Dec 2022 10:31:27 +1100 Subject: [PATCH] feat: Encyrption support for SevenZ Avoid incrasing the public API surface with uneccessary method COMPRESS-633 --- .vscode/settings.json | 3 + .../archivers/sevenz/AES256Options.java | 4 +- .../archivers/sevenz/AES256SHA256Decoder.java | 62 ++++++++++++++----- .../compress/archivers/sevenz/SevenZFile.java | 5 +- .../archivers/sevenz/SevenZOutputFile.java | 3 +- .../commons/compress/utils/ByteUtils.java | 24 ------- 6 files changed, 53 insertions(+), 48 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..e0f15db2eb2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256Options.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256Options.java index 756b52b84aa..a2e4a91a4ed 100644 --- a/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256Options.java +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256Options.java @@ -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); } @@ -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; diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256SHA256Decoder.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256SHA256Decoder.java index 54257fe972d..b7be11cdc29 100644 --- a/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256SHA256Decoder.java +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/AES256SHA256Decoder.java @@ -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; @@ -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 { @@ -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; } } diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java index 4069b26e60e..a7d938d55b6 100644 --- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java @@ -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; @@ -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); } /** @@ -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); } /** diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java index 7cac186586e..14080640d77 100644 --- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java @@ -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; @@ -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); } } diff --git a/src/main/java/org/apache/commons/compress/utils/ByteUtils.java b/src/main/java/org/apache/commons/compress/utils/ByteUtils.java index bceebc95b5a..254643231b9 100644 --- a/src/main/java/org/apache/commons/compress/utils/ByteUtils.java +++ b/src/main/java/org/apache/commons/compress/utils/ByteUtils.java @@ -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. @@ -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; - } }