• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
18 package org.apache.commons.compress.archivers.sevenz;
19 
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.security.GeneralSecurityException;
23 import java.security.MessageDigest;
24 import java.security.NoSuchAlgorithmException;
25 import javax.crypto.Cipher;
26 import javax.crypto.CipherInputStream;
27 import javax.crypto.SecretKey;
28 import javax.crypto.spec.IvParameterSpec;
29 import javax.crypto.spec.SecretKeySpec;
30 import org.apache.commons.compress.PasswordRequiredException;
31 
32 class AES256SHA256Decoder extends CoderBase {
33     @Override
decode(final String archiveName, final InputStream in, final long uncompressedLength, final Coder coder, final byte[] passwordBytes)34     InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength,
35             final Coder coder, final byte[] passwordBytes) throws IOException {
36         return new InputStream() {
37             private boolean isInitialized = false;
38             private CipherInputStream cipherInputStream = null;
39 
40             private CipherInputStream init() throws IOException {
41                 if (isInitialized) {
42                     return cipherInputStream;
43                 }
44                 final int byte0 = 0xff & coder.properties[0];
45                 final int numCyclesPower = byte0 & 0x3f;
46                 final int byte1 = 0xff & coder.properties[1];
47                 final int ivSize = ((byte0 >> 6) & 1) + (byte1 & 0x0f);
48                 final int saltSize = ((byte0 >> 7) & 1) + (byte1 >> 4);
49                 if (2 + saltSize + ivSize > coder.properties.length) {
50                     throw new IOException("Salt size + IV size too long in " + archiveName);
51                 }
52                 final byte[] salt = new byte[saltSize];
53                 System.arraycopy(coder.properties, 2, salt, 0, saltSize);
54                 final byte[] iv = new byte[16];
55                 System.arraycopy(coder.properties, 2 + saltSize, iv, 0, ivSize);
56 
57                 if (passwordBytes == null) {
58                     throw new PasswordRequiredException(archiveName);
59                 }
60                 final byte[] aesKeyBytes;
61                 if (numCyclesPower == 0x3f) {
62                     aesKeyBytes = new byte[32];
63                     System.arraycopy(salt, 0, aesKeyBytes, 0, saltSize);
64                     System.arraycopy(passwordBytes, 0, aesKeyBytes, saltSize,
65                                      Math.min(passwordBytes.length, aesKeyBytes.length - saltSize));
66                 } else {
67                     final MessageDigest digest;
68                     try {
69                         digest = MessageDigest.getInstance("SHA-256");
70                     } catch (final NoSuchAlgorithmException noSuchAlgorithmException) {
71                         throw new IOException("SHA-256 is unsupported by your Java implementation",
72                             noSuchAlgorithmException);
73                     }
74                     final byte[] extra = new byte[8];
75                     for (long j = 0; j < (1L << numCyclesPower); j++) {
76                         digest.update(salt);
77                         digest.update(passwordBytes);
78                         digest.update(extra);
79                         for (int k = 0; k < extra.length; k++) {
80                             ++extra[k];
81                             if (extra[k] != 0) {
82                                 break;
83                             }
84                         }
85                     }
86                     aesKeyBytes = digest.digest();
87                 }
88 
89                 final SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
90                 try {
91                     final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
92                     cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv));
93                     cipherInputStream = new CipherInputStream(in, cipher);
94                     isInitialized = true;
95                     return cipherInputStream;
96                 } catch (final GeneralSecurityException generalSecurityException) {
97                     throw new IOException("Decryption error " +
98                         "(do you have the JCE Unlimited Strength Jurisdiction Policy Files installed?)",
99                         generalSecurityException);
100                     }
101             }
102 
103             @Override
104             public int read() throws IOException {
105                 return init().read();
106             }
107 
108             @Override
109             public int read(final byte[] b, final int off, final int len) throws IOException {
110                 return init().read(b, off, len);
111             }
112 
113             @Override
114             public void close() {
115             }
116         };
117     }
118 }
119