diff --git a/odfdom/src/main/java/org/odftoolkit/odfdom/pkg/OdfPackage.java b/odfdom/src/main/java/org/odftoolkit/odfdom/pkg/OdfPackage.java index ab1093550..8eeb6d119 100644 --- a/odfdom/src/main/java/org/odftoolkit/odfdom/pkg/OdfPackage.java +++ b/odfdom/src/main/java/org/odftoolkit/odfdom/pkg/OdfPackage.java @@ -156,6 +156,7 @@ public class OdfPackage implements Closeable { private OdfManifestDom mManifestDom; private String mOldPwd; private String mNewPwd; + private boolean isAES = true; /* Commonly used files within the ODF Package */ public enum OdfFile { @@ -1913,17 +1914,18 @@ private byte[] encryptData(byte[] data, OdfFileEntry fileEntry) { // 3. The start key is generated: the byte sequence // representing the password in UTF-8 is used to - // generate a 20-byte SHA1 digest. - byte[] passBytes = mNewPwd.getBytes("UTF-8"); - MessageDigest md = MessageDigest.getInstance("SHA1"); + // generate a 20-byte SHA-1 or a 32-byte SHA-256 digest. + byte[] passBytes = mNewPwd.getBytes(StandardCharsets.UTF_8); + MessageDigest md = MessageDigest.getInstance(isAES ? "SHA-256" : "SHA-1"); passBytes = md.digest(passBytes); + // 4. Checksum specifies a digest in BASE64 encoding // that can be used to detect password correctness. The // digest is build from the compressed unencrypted file. md.reset(); - md.update(compressedData, 0, (compressedDataLength > 1024 ? 1024 : compressedDataLength)); - byte[] checksumBytes = new byte[20]; - md.digest(checksumBytes, 0, 20); + md.update(compressedData, 0, (Math.min(compressedDataLength, 1024))); + byte[] checksumBytes = new byte[isAES ? 32 : 20]; + md.digest(checksumBytes, 0, checksumBytes.length); // 5. For each file, a 16-byte salt is generated by a random // generator. @@ -1932,39 +1934,36 @@ private byte[] encryptData(byte[] data, OdfFileEntry fileEntry) { byte[] salt = new byte[16]; secureRandom.nextBytes(salt); - // char passChars[] = new String(passBytes, "UTF-8").toCharArray(); - /* - * char passChars[] = new char[20]; for (int i = 0; i < - * passBytes.length; i++) { passChars[i] = (char) - * ((passBytes[i]+256)%256); - * //System.out.println("passChars[i]:"+passChars - * [i]+", passBytes[i]"+passBytes[i]); } //char passChars[] = - * getChars(passBytes); // 6. The PBKDF2 algorithm based on the - * HMAC-SHA-1 function is used for the key derivation. - * SecretKeyFactory factory = - * SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); // 7. The - * salt is used together with the start key to derive a unique - * 128-bit key for each file. // The default iteration count for the - * algorithm is 1024. KeySpec spec = new PBEKeySpec(passChars, salt, - * 1024, 128); SecretKey skey = factory.generateSecret(spec); byte[] - * raw = skey.getEncoded(); // algorithm-name="Blowfish CFB" - * SecretKeySpec skeySpec = new SecretKeySpec(raw, "Blowfish"); - */ - byte[] dk = derivePBKDF2Key(passBytes, salt, 1024, 16); - SecretKeySpec key = new SecretKeySpec(dk, "Blowfish"); - // 8.The files are encrypted: The random number - // generator is used to generate the 8-byte initialization vector - // for the - // algorithm. The derived key is used together with the - // initialization - // vector to encrypt the file using the Blowfish algorithm in cipher - // feedback - // CFB mode. - Cipher cipher = Cipher.getInstance("Blowfish/CFB/NoPadding"); + // 6-7. Key derivation: + // The PBKDF2 algorithm based on the HMAC-SHA-1 function is used for the key derivation. + SecretKeySpec key; + if (isAES) { + // The salt is used together with the start key to derive a unique 2048-bit key for each + // file. + // The default iteration count for the algorithm is 100000. + PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA1Digest()); + generator.init(passBytes, salt, 100000); + KeyParameter keyParam = (KeyParameter) generator.generateDerivedParameters(256); + key = new SecretKeySpec(keyParam.getKey(), "AES"); + } else { + // The salt is used together with the start key to derive a unique 128-bit key for each + // file. + // The default iteration count for the algorithm is 1024. + byte[] dk = derivePBKDF2Key(passBytes, salt, 1024, 16); + key = new SecretKeySpec(dk, "Blowfish"); + } + + // 8. The files are encrypted: + // The random number generator is used to generate the 8-byte initialization vector + // for the algorithm. The derived key is used together with the initialization + // vector to encrypt the file using the Blowfish algorithm in cipher feedback + // CFB/CBC mode. + Cipher cipher = + Cipher.getInstance(isAES ? "AES/CBC/ISO10126Padding" : "Blowfish/CFB/NoPadding"); // initialisation-vector specifies the byte-sequence used // as an initialization vector to a encryption algorithm. The // initialization vector is a BASE64 encoded binary sequence. - byte[] iv = new byte[8]; + byte[] iv = new byte[isAES ? 16 : 8]; secureRandom.nextBytes(iv); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec); @@ -1978,7 +1977,10 @@ private byte[] encryptData(byte[] data, OdfFileEntry fileEntry) { if (encryptionDataElement != null) { fileEntryElement.removeChild(encryptionDataElement); } - encryptionDataElement = fileEntryElement.newEncryptionDataElement(checksum, "SHA1/1K"); + encryptionDataElement = + fileEntryElement.newEncryptionDataElement( + checksum, + isAES ? "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k" : "SHA1/1K"); String initialisationVector = new Base64Binary(iv).toString(); AlgorithmElement algorithmElement = OdfElement.findFirstChildNode(AlgorithmElement.class, encryptionDataElement); @@ -1986,20 +1988,25 @@ private byte[] encryptData(byte[] data, OdfFileEntry fileEntry) { encryptionDataElement.removeChild(algorithmElement); } algorithmElement = - encryptionDataElement.newAlgorithmElement("Blowfish CFB", initialisationVector); + encryptionDataElement.newAlgorithmElement( + isAES ? "http://www.w3.org/2001/04/xmlenc#aes256-cbc" : "Blowfish CFB", + initialisationVector); String saltStr = new Base64Binary(salt).toString(); KeyDerivationElement keyDerivationElement = OdfElement.findFirstChildNode(KeyDerivationElement.class, encryptionDataElement); if (keyDerivationElement != null) { encryptionDataElement.removeChild(keyDerivationElement); } - keyDerivationElement = encryptionDataElement.newKeyDerivationElement(1024, "PBKDF2", saltStr); + keyDerivationElement = + encryptionDataElement.newKeyDerivationElement(isAES ? 100000 : 1024, "PBKDF2", saltStr); StartKeyGenerationElement startKeyGenerationElement = OdfElement.findFirstChildNode(StartKeyGenerationElement.class, encryptionDataElement); if (startKeyGenerationElement != null) { encryptionDataElement.removeChild(startKeyGenerationElement); } - encryptionDataElement.newStartKeyGenerationElement("SHA1").setKeySizeAttribute(20); + encryptionDataElement + .newStartKeyGenerationElement(isAES ? "http://www.w3.org/2000/09/xmldsig#sha256" : "SHA1") + .setKeySizeAttribute(isAES ? 32 : 20); // System.out.println("full-path=\""+ path +"\""); // System.out.println("size=\""+ data.length +"\""); @@ -2033,7 +2040,7 @@ private byte[] decryptData( byte[] passBytes = mOldPwd.getBytes(StandardCharsets.UTF_8); String algorithm = algorithmElement.getAlgorithmNameAttribute(); - boolean isAES = algorithm.contains("aes256-cbc") || algorithm.contains("AES-256-CBC"); + isAES = algorithm.contains("aes256-cbc") || algorithm.contains("AES-256-CBC"); SecretKeySpec key; Cipher cipher; @@ -2047,7 +2054,7 @@ private byte[] decryptData( generator.init(passBytes, salt, 100000); KeyParameter keyParam = (KeyParameter) generator.generateDerivedParameters(256); key = new SecretKeySpec(keyParam.getKey(), "AES"); - cipher = Cipher.getInstance("AES/CBC/NoPadding"); + cipher = Cipher.getInstance("AES/CBC/ISO10126Padding"); md.reset(); } else { // Blowfish