View | Details | Raw Unified | Return to bug 49311
Collapse All | Expand All

(-)a/src/java/org/apache/poi/poifs/crypt/Decryptor.java (+114 lines)
Line 0 Link Here
1
package org.apache.poi.poifs.crypt;
2
3
import org.apache.poi.poifs.filesystem.DocumentInputStream;
4
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
5
import org.apache.poi.util.LittleEndian;
6
7
import javax.crypto.Cipher;
8
import javax.crypto.CipherInputStream;
9
import javax.crypto.SecretKey;
10
import javax.crypto.spec.SecretKeySpec;
11
import java.io.IOException;
12
import java.io.InputStream;
13
import java.nio.charset.Charset;
14
import java.security.GeneralSecurityException;
15
import java.security.MessageDigest;
16
import java.security.NoSuchAlgorithmException;
17
import java.util.Arrays;
18
19
public class Decryptor {
20
    public static final String DEFAULT_PASSWORD="VelvetSweatshop";
21
22
    private final EncryptionInfo info;
23
    private byte[] passwordHash;
24
25
    public Decryptor(EncryptionInfo info) {
26
        this.info = info;
27
    }
28
29
    private void generatePasswordHash(String password) throws NoSuchAlgorithmException {
30
        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
31
32
        sha1.update(info.getVerifier().getSalt());
33
        byte[] hash = sha1.digest(password.getBytes(Charset.forName("UTF-16LE")));
34
35
        byte[] iterator = new byte[4];
36
        for (int i = 0; i<50000; i++) {
37
            sha1.reset();
38
39
            LittleEndian.putInt(iterator, i);
40
            sha1.update(iterator);
41
            hash = sha1.digest(hash);
42
        }
43
44
        passwordHash = hash;
45
    }
46
47
    private byte[] generateKey(int block) throws NoSuchAlgorithmException {
48
        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
49
50
        sha1.update(passwordHash);
51
        byte[] blockValue = new byte[4];
52
        LittleEndian.putInt(blockValue, block);
53
        byte[] finalHash = sha1.digest(blockValue);
54
55
        int requiredKeyLength = info.getHeader().getKeySize()/8;
56
57
        byte[] buff = new byte[64];
58
59
        Arrays.fill(buff, (byte) 0x36);
60
61
        for (int i=0; i<finalHash.length; i++) {
62
            buff[i] = (byte) (buff[i] ^ finalHash[i]);
63
        }
64
65
        sha1.reset();
66
        byte[] x1 = sha1.digest(buff);
67
68
        Arrays.fill(buff, (byte) 0x5c);
69
        for (int i=0; i<finalHash.length; i++) {
70
            buff[i] = (byte) (buff[i] ^ finalHash[i]);
71
        }
72
73
        sha1.reset();
74
        byte[] x2 = sha1.digest(buff);
75
76
        byte[] x3 = new byte[x1.length + x2.length];
77
        System.arraycopy(x1, 0, x3, 0, x1.length);
78
        System.arraycopy(x2, 0, x3, x1.length, x2.length);
79
80
        return Arrays.copyOf(x3, requiredKeyLength);
81
    }
82
83
    public boolean verifyPassword(String password) throws GeneralSecurityException {
84
        generatePasswordHash(password);
85
86
        Cipher cipher = getCipher();
87
88
        byte[] verifier = cipher.doFinal(info.getVerifier().getVerifier());
89
90
        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
91
        byte[] calcVerifierHash = sha1.digest(verifier);
92
93
        byte[] verifierHash = Arrays.copyOf(cipher.doFinal(info.getVerifier().getVerifierHash()), calcVerifierHash.length);
94
95
        return Arrays.equals(calcVerifierHash, verifierHash);
96
    }
97
98
    private Cipher getCipher() throws GeneralSecurityException {
99
        byte[] key = generateKey(0);
100
        Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
101
        SecretKey skey = new SecretKeySpec(key, "AES");
102
        cipher.init(Cipher.DECRYPT_MODE, skey);
103
104
        return cipher;
105
    }
106
107
    public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
108
        DocumentInputStream dis = fs.createDocumentInputStream("EncryptedPackage");
109
110
        long size = dis.readLong();
111
112
        return new CipherInputStream(dis, getCipher());
113
    }
114
}
(-)a/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java (+78 lines)
Line 0 Link Here
1
package org.apache.poi.poifs.crypt;
2
3
import org.apache.poi.poifs.filesystem.DocumentInputStream;
4
5
import java.io.IOException;
6
7
public class EncryptionHeader {
8
    public static final int ALGORITHM_RC4 = 0x6801;
9
    public static final int ALGORITHM_AES_128 = 0x660E;
10
    public static final int ALGORITHM_AES_192 = 0x660F;
11
    public static final int ALGORITHM_AES_256 = 0x6610;
12
13
    public static final int HASH_SHA1 = 0x8004;
14
15
    public static final int PROVIDER_RC4 = 1;
16
    public static final int PROVIDER_AES = 0x18; 
17
18
    private final int flags;
19
    private final int sizeExtra;
20
    private final int algorithm;
21
    private final int hashAlgorithm;
22
    private final int keySize;
23
    private final int providerType;
24
    private final String cspName;
25
26
    public EncryptionHeader(DocumentInputStream is) throws IOException {
27
        flags = is.readInt();
28
        sizeExtra = is.readInt();
29
        algorithm = is.readInt();
30
        hashAlgorithm = is.readInt();
31
        keySize = is.readInt();
32
        providerType = is.readInt();
33
34
        is.readLong(); // skip reserved
35
36
        StringBuilder builder = new StringBuilder();
37
38
        while (true) {
39
            char c = (char) is.readShort();
40
41
            if (c == 0) {
42
                break;
43
            }
44
45
            builder.append(c);
46
        }
47
48
        cspName = builder.toString();
49
    }
50
51
    public int getFlags() {
52
        return flags;
53
    }
54
55
    public int getSizeExtra() {
56
        return sizeExtra;
57
    }
58
59
    public int getAlgorithm() {
60
        return algorithm;
61
    }
62
63
    public int getHashAlgorithm() {
64
        return hashAlgorithm;
65
    }
66
67
    public int getKeySize() {
68
        return keySize;
69
    }
70
71
    public int getProviderType() {
72
        return providerType;
73
    }
74
75
    public String getCspName() {
76
        return cspName;
77
    }
78
}
(-)a/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java (+53 lines)
Line 0 Link Here
1
package org.apache.poi.poifs.crypt;
2
3
import org.apache.poi.poifs.filesystem.DocumentInputStream;
4
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
5
6
import java.io.IOException;
7
8
public class EncryptionInfo {
9
    private final int versionMajor;
10
    private final int versionMinor;
11
    private final int encryptionFlags;
12
13
    private final EncryptionHeader header;
14
    private final EncryptionVerifier verifier;
15
16
    public EncryptionInfo(POIFSFileSystem fs) throws IOException {
17
        DocumentInputStream dis = fs.createDocumentInputStream("EncryptionInfo");
18
19
        versionMajor = dis.readShort();
20
        versionMinor = dis.readShort();
21
        encryptionFlags = dis.readInt();
22
23
        int hSize = dis.readInt();
24
25
        header = new EncryptionHeader(dis);
26
27
        if (header.getAlgorithm()==EncryptionHeader.ALGORITHM_RC4) {
28
            verifier = new EncryptionVerifier(dis, 20);
29
        } else {
30
            verifier = new EncryptionVerifier(dis, 32);            
31
        }
32
    }
33
34
    public int getVersionMajor() {
35
        return versionMajor;
36
    }
37
38
    public int getVersionMinor() {
39
        return versionMinor;
40
    }
41
42
    public int getEncryptionFlags() {
43
        return encryptionFlags;
44
    }
45
46
    public EncryptionHeader getHeader() {
47
        return header;
48
    }
49
50
    public EncryptionVerifier getVerifier() {
51
        return verifier;
52
    }
53
}
(-)a/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java (+38 lines)
Line 0 Link Here
1
package org.apache.poi.poifs.crypt;
2
3
import org.apache.poi.poifs.filesystem.DocumentInputStream;
4
5
public class EncryptionVerifier {
6
    private final byte[] salt = new byte[16];
7
    private final byte[] verifier = new byte[16];
8
    private final byte[] verifierHash;
9
    private final int verifierHashSize;
10
11
    public EncryptionVerifier(DocumentInputStream is, int encryptedLength) {
12
        int saltSize = is.readInt();
13
14
        if (saltSize!=16) {
15
            throw new RuntimeException("Salt size != 16 !?");
16
        }
17
18
        is.readFully(salt);
19
        is.readFully(verifier);
20
21
        verifierHashSize = is.readInt();
22
23
        verifierHash = new byte[encryptedLength];
24
        is.readFully(verifierHash);
25
    }
26
27
    public byte[] getSalt() {
28
        return salt;
29
    }
30
31
    public byte[] getVerifier() {
32
        return verifier;
33
    }
34
35
    public byte[] getVerifierHash() {
36
        return verifierHash;
37
    }
38
}
(-)a/src/testcases/org/apache/poi/poifs/crypt/DecryptorTest.java (+49 lines)
Line 0 Link Here
1
package org.apache.poi.poifs.crypt;
2
3
import junit.framework.TestCase;
4
import org.apache.poi.POIDataSamples;
5
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
6
7
import java.io.IOException;
8
import java.security.GeneralSecurityException;
9
import java.util.zip.ZipEntry;
10
import java.util.zip.ZipInputStream;
11
12
public class DecryptorTest extends TestCase {
13
    public void testPasswordVerification() throws IOException, GeneralSecurityException {
14
        POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
15
16
        EncryptionInfo info = new EncryptionInfo(fs);
17
18
        Decryptor d = new Decryptor(info);
19
20
        assertTrue(d.verifyPassword(Decryptor.DEFAULT_PASSWORD));
21
    }
22
23
    public void testDecrypt() throws IOException, GeneralSecurityException {
24
        POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
25
26
        EncryptionInfo info = new EncryptionInfo(fs);
27
28
        Decryptor d = new Decryptor(info);
29
30
        d.verifyPassword(Decryptor.DEFAULT_PASSWORD);
31
32
        zipOk(fs, d);
33
    }
34
35
    private void zipOk(POIFSFileSystem fs, Decryptor d) throws IOException, GeneralSecurityException {
36
        ZipInputStream zin = new ZipInputStream(d.getDataStream(fs));
37
38
        while (true) {
39
            ZipEntry entry = zin.getNextEntry();
40
            if (entry==null) {
41
                break;
42
            }
43
44
            while (zin.available()>0) {
45
                zin.skip(zin.available());
46
            }
47
        }
48
    }
49
}
(-)a/src/testcases/org/apache/poi/poifs/crypt/EncryptionInfoTest.java (+26 lines)
Line 0 Link Here
1
package org.apache.poi.poifs.crypt;
2
3
import junit.framework.TestCase;
4
import org.apache.poi.POIDataSamples;
5
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
6
7
import java.io.IOException;
8
9
public class EncryptionInfoTest extends TestCase {
10
    public void testEncryptionInfo() throws IOException {
11
        POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
12
13
        EncryptionInfo info = new EncryptionInfo(fs);
14
15
        assertEquals(3, info.getVersionMajor());
16
        assertEquals(2, info.getVersionMinor());
17
18
        assertEquals(EncryptionHeader.ALGORITHM_AES_128, info.getHeader().getAlgorithm());
19
        assertEquals(EncryptionHeader.HASH_SHA1, info.getHeader().getHashAlgorithm());
20
        assertEquals(128, info.getHeader().getKeySize());
21
        assertEquals(EncryptionHeader.PROVIDER_AES, info.getHeader().getProviderType());                
22
        assertEquals("Microsoft Enhanced RSA and AES Cryptographic Provider", info.getHeader().getCspName());
23
24
        assertEquals(32, info.getVerifier().getVerifierHash().length);
25
    }
26
}

Return to bug 49311