AES256SHA256Decoder.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.commons.compress.archivers.sevenz;

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

  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.nio.ByteBuffer;
  23. import java.nio.CharBuffer;
  24. import java.security.GeneralSecurityException;
  25. import java.security.MessageDigest;
  26. import java.security.NoSuchAlgorithmException;
  27. import java.util.Arrays;

  28. import javax.crypto.Cipher;
  29. import javax.crypto.CipherInputStream;
  30. import javax.crypto.CipherOutputStream;
  31. import javax.crypto.SecretKey;
  32. import javax.crypto.spec.IvParameterSpec;

  33. import org.apache.commons.compress.PasswordRequiredException;

  34. final class AES256SHA256Decoder extends AbstractCoder {

  35.     private static final class AES256SHA256DecoderInputStream extends InputStream {
  36.         private final InputStream in;
  37.         private final Coder coder;
  38.         private final String archiveName;
  39.         private final byte[] passwordBytes;
  40.         private boolean isInitialized;
  41.         private CipherInputStream cipherInputStream;

  42.         private AES256SHA256DecoderInputStream(final InputStream in, final Coder coder, final String archiveName, final byte[] passwordBytes) {
  43.             this.in = in;
  44.             this.coder = coder;
  45.             this.archiveName = archiveName;
  46.             this.passwordBytes = passwordBytes;
  47.         }

  48.         @Override
  49.         public void close() throws IOException {
  50.             if (cipherInputStream != null) {
  51.                 cipherInputStream.close();
  52.             }
  53.         }

  54.         private CipherInputStream init() throws IOException {
  55.             if (isInitialized) {
  56.                 return cipherInputStream;
  57.             }
  58.             if (coder.properties == null) {
  59.                 throw new IOException("Missing AES256 properties in " + archiveName);
  60.             }
  61.             if (coder.properties.length < 2) {
  62.                 throw new IOException("AES256 properties too short in " + archiveName);
  63.             }
  64.             final int byte0 = 0xff & coder.properties[0];
  65.             final int numCyclesPower = byte0 & 0x3f;
  66.             final int byte1 = 0xff & coder.properties[1];
  67.             final int ivSize = (byte0 >> 6 & 1) + (byte1 & 0x0f);
  68.             final int saltSize = (byte0 >> 7 & 1) + (byte1 >> 4);
  69.             if (2 + saltSize + ivSize > coder.properties.length) {
  70.                 throw new IOException("Salt size + IV size too long in " + archiveName);
  71.             }
  72.             final byte[] salt = new byte[saltSize];
  73.             System.arraycopy(coder.properties, 2, salt, 0, saltSize);
  74.             final byte[] iv = new byte[16];
  75.             System.arraycopy(coder.properties, 2 + saltSize, iv, 0, ivSize);

  76.             if (passwordBytes == null) {
  77.                 throw new PasswordRequiredException(archiveName);
  78.             }
  79.             final byte[] aesKeyBytes;
  80.             if (numCyclesPower == 0x3f) {
  81.                 aesKeyBytes = new byte[32];
  82.                 System.arraycopy(salt, 0, aesKeyBytes, 0, saltSize);
  83.                 System.arraycopy(passwordBytes, 0, aesKeyBytes, saltSize, Math.min(passwordBytes.length, aesKeyBytes.length - saltSize));
  84.             } else {
  85.                 aesKeyBytes = sha256Password(passwordBytes, numCyclesPower, salt);
  86.             }

  87.             final SecretKey aesKey = AES256Options.newSecretKeySpec(aesKeyBytes);
  88.             try {
  89.                 final Cipher cipher = Cipher.getInstance(AES256Options.TRANSFORMATION);
  90.                 cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv));
  91.                 cipherInputStream = new CipherInputStream(in, cipher);
  92.                 isInitialized = true;
  93.                 return cipherInputStream;
  94.             } catch (final GeneralSecurityException generalSecurityException) {
  95.                 throw new IllegalStateException("Decryption error (do you have the JCE Unlimited Strength Jurisdiction Policy Files installed?)",
  96.                         generalSecurityException);
  97.             }
  98.         }

  99.         @SuppressWarnings("resource") // Closed in close()
  100.         @Override
  101.         public int read() throws IOException {
  102.             return init().read();
  103.         }

  104.         @SuppressWarnings("resource") // Closed in close()
  105.         @Override
  106.         public int read(final byte[] b, final int off, final int len) throws IOException {
  107.             return init().read(b, off, len);
  108.         }
  109.     }

  110.     private static final class AES256SHA256DecoderOutputStream extends OutputStream {
  111.         private final CipherOutputStream cipherOutputStream;
  112.         // Ensures that data are encrypt in respect of cipher block size and pad with '0' if smaller
  113.         // NOTE: As "AES/CBC/PKCS5Padding" is weak and should not be used, we use "AES/CBC/NoPadding" with this
  114.         // manual implementation for padding possible thanks to the size of the file stored separately
  115.         private final int cipherBlockSize;
  116.         private final byte[] cipherBlockBuffer;
  117.         private int count;

  118.         private AES256SHA256DecoderOutputStream(final AES256Options opts, final OutputStream out) {
  119.             cipherOutputStream = new CipherOutputStream(out, opts.getCipher());
  120.             cipherBlockSize = opts.getCipher().getBlockSize();
  121.             cipherBlockBuffer = new byte[cipherBlockSize];
  122.         }

  123.         @Override
  124.         public void close() throws IOException {
  125.             if (count > 0) {
  126.                 cipherOutputStream.write(cipherBlockBuffer);
  127.             }
  128.             cipherOutputStream.close();
  129.         }

  130.         @Override
  131.         public void flush() throws IOException {
  132.             cipherOutputStream.flush();
  133.         }

  134.         private void flushBuffer() throws IOException {
  135.             cipherOutputStream.write(cipherBlockBuffer);
  136.             count = 0;
  137.             Arrays.fill(cipherBlockBuffer, (byte) 0);
  138.         }

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

  144.             if (count == cipherBlockSize) {
  145.                 flushBuffer();

  146.                 if (len - gap >= cipherBlockSize) {
  147.                     // skip buffer to encrypt data chunks big enough to fit cipher block size
  148.                     final int multipleCipherBlockSizeLen = (len - gap) / cipherBlockSize * cipherBlockSize;
  149.                     cipherOutputStream.write(b, off + gap, multipleCipherBlockSizeLen);
  150.                     gap += multipleCipherBlockSizeLen;
  151.                 }
  152.                 System.arraycopy(b, off + gap, cipherBlockBuffer, 0, len - gap);
  153.                 count = len - gap;
  154.             }
  155.         }

  156.         @Override
  157.         public void write(final int b) throws IOException {
  158.             cipherBlockBuffer[count++] = (byte) b;
  159.             if (count == cipherBlockSize) {
  160.                 flushBuffer();
  161.             }
  162.         }
  163.     }

  164.     static byte[] sha256Password(final byte[] password, final int numCyclesPower, final byte[] salt) {
  165.         final MessageDigest digest;
  166.         try {
  167.             digest = MessageDigest.getInstance("SHA-256");
  168.         } catch (final NoSuchAlgorithmException noSuchAlgorithmException) {
  169.             throw new IllegalStateException("SHA-256 is unsupported by your Java implementation", noSuchAlgorithmException);
  170.         }
  171.         final byte[] extra = new byte[8];
  172.         for (long j = 0; j < 1L << numCyclesPower; j++) {
  173.             digest.update(salt);
  174.             digest.update(password);
  175.             digest.update(extra);
  176.             for (int k = 0; k < extra.length; k++) {
  177.                 ++extra[k];
  178.                 if (extra[k] != 0) {
  179.                     break;
  180.                 }
  181.             }
  182.         }
  183.         return digest.digest();
  184.     }

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

  188.     /**
  189.      * Convenience method that encodes Unicode characters into bytes in UTF-16 (little-endian byte order) charset
  190.      *
  191.      * @param chars characters to encode
  192.      * @return encoded characters
  193.      * @since 1.23
  194.      */
  195.     static byte[] utf16Decode(final char[] chars) {
  196.         if (chars == null) {
  197.             return null;
  198.         }
  199.         final ByteBuffer encoded = UTF_16LE.encode(CharBuffer.wrap(chars));
  200.         if (encoded.hasArray()) {
  201.             return encoded.array();
  202.         }
  203.         final byte[] e = new byte[encoded.remaining()];
  204.         encoded.get(e);
  205.         return e;
  206.     }

  207.     AES256SHA256Decoder() {
  208.         super(AES256Options.class);
  209.     }

  210.     @Override
  211.     InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] passwordBytes,
  212.             final int maxMemoryLimitInKb) {
  213.         return new AES256SHA256DecoderInputStream(in, coder, archiveName, passwordBytes);
  214.     }

  215.     @Override
  216.     OutputStream encode(final OutputStream out, final Object options) throws IOException {
  217.         return new AES256SHA256DecoderOutputStream((AES256Options) options, out);
  218.     }

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

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

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

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

  232.         return props;
  233.     }
  234. }