From ed36a35d722e24ab60c48d2a677e42a1d89998a0 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 18:59:24 +0200 Subject: [PATCH 1/6] Componentize --- .../components/cipher/PlexusCipher.java | 74 +++++----- ...PBECipher.java => AESCBCPKCS5Padding.java} | 76 +++------- .../cipher/internal/AESGCMNoPadding.java | 113 +++++++++++++++ .../components/cipher/internal/Cipher.java | 37 +++++ .../cipher/internal/DefaultPlexusCipher.java | 82 ++++++----- .../internal/AESCBCPKCS5PaddingTest.java | 8 ++ .../cipher/internal/AESGCMNoPaddingTest.java | 8 ++ ...CipherTest.java => CipherTestSupport.java} | 39 ++---- .../internal/DefaultPlexusCipherTest.java | 130 +++++++----------- 9 files changed, 340 insertions(+), 227 deletions(-) rename src/main/java/org/codehaus/plexus/components/cipher/internal/{PBECipher.java => AESCBCPKCS5Padding.java} (75%) create mode 100644 src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java create mode 100644 src/main/java/org/codehaus/plexus/components/cipher/internal/Cipher.java create mode 100644 src/test/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5PaddingTest.java create mode 100644 src/test/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPaddingTest.java rename src/test/java/org/codehaus/plexus/components/cipher/internal/{PBECipherTest.java => CipherTestSupport.java} (73%) diff --git a/src/main/java/org/codehaus/plexus/components/cipher/PlexusCipher.java b/src/main/java/org/codehaus/plexus/components/cipher/PlexusCipher.java index 390633b..53971ab 100644 --- a/src/main/java/org/codehaus/plexus/components/cipher/PlexusCipher.java +++ b/src/main/java/org/codehaus/plexus/components/cipher/PlexusCipher.java @@ -12,74 +12,76 @@ */ package org.codehaus.plexus.components.cipher; +import java.util.Set; + /** * @author Oleg Gusakov */ public interface PlexusCipher { + + /** + * Returns the available cipher algorithms, never {@code null}. + */ + Set availableCiphers(); + /** - * encrypt given string with the given passPhrase and encode it into base64 + * Encrypt given string with the given alg and passPhrase and encode it into Base64 string. * - * @param str string to encrypt - * @param passPhrase pass phrase - * @return encrypted str + * @param alg cipher alg to use, never {@code null} + * @param str string to encrypt, never {@code null} + * @param passPhrase pass phrase, never {@code null} + * @return encrypted str, never {@code null} * @throws PlexusCipherException if encryption fails */ - String encrypt(String str, String passPhrase) throws PlexusCipherException; + String encrypt(String alg, String str, String passPhrase) throws PlexusCipherException; /** - * encrypt given string with the given passPhrase, encode it into base64 and return result, wrapped into { } - * decorations + * Encrypt given string with the given alg and passPhrase and encode it into Base64 decorated string. * - * @param str string to encrypt - * @param passPhrase pass phrase - * @return encrypted and decorated str + * @param alg cipher alg to use, never {@code null} + * @param str string to encrypt, never {@code null} + * @param passPhrase pass phrase, never {@code null} + * @return encrypted and decorated str, never {@code null} * @throws PlexusCipherException if encryption fails */ - String encryptAndDecorate(String str, String passPhrase) throws PlexusCipherException; + String encryptAndDecorate(String alg, String str, String passPhrase) throws PlexusCipherException; /** - * decrypt given base64 encrypted string + * Decrypt given Base64 encoded string with the given alg and passPhrase and return resulting string. * - * @param str base64 encoded string - * @param passPhrase pass phrase - * @return decrypted str - * @throws PlexusCipherException if decryption fails + * @param alg cipher alg to use, never {@code null} + * @param str string to encrypt, never {@code null} + * @param passPhrase pass phrase, never {@code null} + * @return encrypted and decorated str, never {@code null} + * @throws PlexusCipherException if encryption fails */ - String decrypt(String str, String passPhrase) throws PlexusCipherException; + String decrypt(String alg, String str, String passPhrase) throws PlexusCipherException; /** - * decrypt given base64 encoded encrypted string. If string is decorated, decrypt base64 encoded string inside - * decorations + * Decrypt given decorated Base64 encoded string with the given alg and passPhrase and return resulting string. * - * @param str base64 encoded string - * @param passPhrase pass phrase - * @return decrypted decorated str - * @throws PlexusCipherException if decryption fails + * @param alg cipher alg to use, never {@code null} + * @param str string to encrypt, never {@code null} + * @param passPhrase pass phrase, never {@code null} + * @return encrypted and decorated str, never {@code null} + * @throws PlexusCipherException if encryption fails */ - String decryptDecorated(String str, String passPhrase) throws PlexusCipherException; + String decryptDecorated(String alg, String str, String passPhrase) throws PlexusCipherException; /** - * check if given string is decorated - * - * @param str string to check - * @return true if string is encrypted + * Check if given string is decorated. */ boolean isEncryptedString(String str); /** - * return string inside decorations + * Remove decorations from string, if it was decorated. * - * @param str decorated string - * @return undecorated str - * @throws PlexusCipherException if decryption fails + * @throws PlexusCipherException is string is malformed */ String unDecorate(String str) throws PlexusCipherException; /** - * decorated given string with { and } - * - * @param str string to decorate - * @return decorated str + * Decorates given string. */ String decorate(String str); } diff --git a/src/main/java/org/codehaus/plexus/components/cipher/internal/PBECipher.java b/src/main/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5Padding.java similarity index 75% rename from src/main/java/org/codehaus/plexus/components/cipher/internal/PBECipher.java rename to src/main/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5Padding.java index c8a3838..9b6fd97 100644 --- a/src/main/java/org/codehaus/plexus/components/cipher/internal/PBECipher.java +++ b/src/main/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5Padding.java @@ -25,8 +25,9 @@ Licensed to the Apache Software Foundation (ASF) under one import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; +import javax.inject.Named; +import javax.inject.Singleton; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -38,107 +39,74 @@ Licensed to the Apache Software Foundation (ASF) under one import org.codehaus.plexus.components.cipher.PlexusCipherException; -/** - * This class is thread-safe. - * - * @author Oleg Gusakov - */ -public class PBECipher { - protected static final Charset STRING_ENCODING = StandardCharsets.UTF_8; - protected static final int SPICE_SIZE = 16; - protected static final int SALT_SIZE = 8; - protected static final int CHUNK_SIZE = 16; - protected static final String KEY_ALG = "AES"; - protected static final String CIPHER_ALG = "AES/CBC/PKCS5Padding"; - protected static final int PBE_ITERATIONS = 310000; +@Singleton +@Named(AESCBCPKCS5Padding.CIPHER_ALG) +public class AESCBCPKCS5Padding implements org.codehaus.plexus.components.cipher.internal.Cipher { + public static final String CIPHER_ALG = "AES/CBC/PKCS5Padding"; + + private static final int SPICE_SIZE = 16; + private static final int SALT_SIZE = 8; + private static final int CHUNK_SIZE = 16; + private static final String KEY_ALG = "AES"; + private static final int PBE_ITERATIONS = 310000; private static final SecureRandom _secureRandom = new SecureRandom(); - // --------------------------------------------------------------- - private byte[] getSalt(final int sz) { + private byte[] getSalt(int sz) { byte[] res = new byte[sz]; - _secureRandom.nextBytes(res); - return res; } - // ------------------------------------------------------------------------------- - public String encrypt64(final String clearText, final String password) throws PlexusCipherException { - try { - byte[] clearBytes = clearText.getBytes(STRING_ENCODING); + @Override + public String encrypt(String clearText, String password) throws PlexusCipherException { + try { + byte[] clearBytes = clearText.getBytes(StandardCharsets.UTF_8); byte[] salt = getSalt(SALT_SIZE); - Cipher cipher = createCipher(password.toCharArray(), salt, Cipher.ENCRYPT_MODE); - byte[] encryptedBytes = cipher.doFinal(clearBytes); - int len = encryptedBytes.length; - byte padLen = (byte) (CHUNK_SIZE - (SALT_SIZE + len + 1) % CHUNK_SIZE); - int totalLen = SALT_SIZE + len + padLen + 1; - byte[] allEncryptedBytes = getSalt(totalLen); - System.arraycopy(salt, 0, allEncryptedBytes, 0, SALT_SIZE); - allEncryptedBytes[SALT_SIZE] = padLen; - System.arraycopy(encryptedBytes, 0, allEncryptedBytes, SALT_SIZE + 1, len); - return Base64.getEncoder().encodeToString(allEncryptedBytes); } catch (Exception e) { throw new PlexusCipherException(e.getMessage(), e); } } - // ------------------------------------------------------------------------------- - public String decrypt64(final String encryptedText, final String password) throws PlexusCipherException { + @Override + public String decrypt(String encryptedText, String password) throws PlexusCipherException { try { byte[] allEncryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes()); - int totalLen = allEncryptedBytes.length; - byte[] salt = new byte[SALT_SIZE]; - System.arraycopy(allEncryptedBytes, 0, salt, 0, SALT_SIZE); - byte padLen = allEncryptedBytes[SALT_SIZE]; - byte[] encryptedBytes = new byte[totalLen - SALT_SIZE - 1 - padLen]; - System.arraycopy(allEncryptedBytes, SALT_SIZE + 1, encryptedBytes, 0, encryptedBytes.length); - Cipher cipher = createCipher(password.toCharArray(), salt, Cipher.DECRYPT_MODE); - byte[] clearBytes = cipher.doFinal(encryptedBytes); - - return new String(clearBytes, STRING_ENCODING); + return new String(clearBytes, StandardCharsets.UTF_8); } catch (Exception e) { throw new PlexusCipherException(e.getMessage(), e); } } - // ------------------------------------------------------------------------------- - private Cipher createCipher(final char[] pwd, byte[] salt, final int mode) + + private Cipher createCipher(char[] pwd, byte[] salt, int mode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, InvalidKeySpecException { - KeySpec spec = new PBEKeySpec(pwd, salt, PBE_ITERATIONS, SPICE_SIZE * 16); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); byte[] keyAndIv = factory.generateSecret(spec).getEncoded(); - byte[] key = new byte[SPICE_SIZE]; - byte[] iv = new byte[SPICE_SIZE]; - System.arraycopy(keyAndIv, 0, key, 0, key.length); - System.arraycopy(keyAndIv, key.length, iv, 0, iv.length); - Cipher cipher = Cipher.getInstance(CIPHER_ALG); - cipher.init(mode, new SecretKeySpec(key, KEY_ALG), new IvParameterSpec(iv)); - return cipher; } } diff --git a/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java b/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java new file mode 100644 index 0000000..59a28c4 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java @@ -0,0 +1,113 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + */ + +package org.codehaus.plexus.components.cipher.internal; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Base64; + +import org.codehaus.plexus.components.cipher.PlexusCipherException; + +@Singleton +@Named(AESGCMNoPadding.CIPHER_ALG) +public class AESGCMNoPadding implements org.codehaus.plexus.components.cipher.internal.Cipher { + public static final String CIPHER_ALG = "AES/GCM/NoPadding"; + + private static final int SPICE_SIZE = 16; + private static final int SALT_SIZE = 8; + private static final int CHUNK_SIZE = 16; + private static final String KEY_ALG = "AES"; + private static final int PBE_ITERATIONS = 310000; + private static final SecureRandom _secureRandom = new SecureRandom(); + + private byte[] getSalt(int sz) { + byte[] res = new byte[sz]; + _secureRandom.nextBytes(res); + return res; + } + + @Override + public String encrypt(String clearText, String password) throws PlexusCipherException { + try { + byte[] clearBytes = clearText.getBytes(StandardCharsets.UTF_8); + byte[] salt = getSalt(SALT_SIZE); + Cipher cipher = createCipher(password.toCharArray(), salt, Cipher.ENCRYPT_MODE); + byte[] encryptedBytes = cipher.doFinal(clearBytes); + int len = encryptedBytes.length; + byte padLen = (byte) (CHUNK_SIZE - (SALT_SIZE + len + 1) % CHUNK_SIZE); + int totalLen = SALT_SIZE + len + padLen + 1; + byte[] allEncryptedBytes = getSalt(totalLen); + System.arraycopy(salt, 0, allEncryptedBytes, 0, SALT_SIZE); + allEncryptedBytes[SALT_SIZE] = padLen; + System.arraycopy(encryptedBytes, 0, allEncryptedBytes, SALT_SIZE + 1, len); + return Base64.getEncoder().encodeToString(allEncryptedBytes); + } catch (Exception e) { + throw new PlexusCipherException(e.getMessage(), e); + } + } + + @Override + public String decrypt(String encryptedText, String password) throws PlexusCipherException { + try { + byte[] allEncryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes()); + int totalLen = allEncryptedBytes.length; + byte[] salt = new byte[SALT_SIZE]; + System.arraycopy(allEncryptedBytes, 0, salt, 0, SALT_SIZE); + byte padLen = allEncryptedBytes[SALT_SIZE]; + byte[] encryptedBytes = new byte[totalLen - SALT_SIZE - 1 - padLen]; + System.arraycopy(allEncryptedBytes, SALT_SIZE + 1, encryptedBytes, 0, encryptedBytes.length); + Cipher cipher = createCipher(password.toCharArray(), salt, Cipher.DECRYPT_MODE); + byte[] clearBytes = cipher.doFinal(encryptedBytes); + return new String(clearBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new PlexusCipherException(e.getMessage(), e); + } + } + + private Cipher createCipher(char[] pwd, byte[] salt, int mode) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + InvalidAlgorithmParameterException, InvalidKeySpecException { + KeySpec spec = new PBEKeySpec(pwd, salt, PBE_ITERATIONS, SPICE_SIZE * 16); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + byte[] keyAndIv = factory.generateSecret(spec).getEncoded(); + byte[] key = new byte[SPICE_SIZE]; + byte[] iv = new byte[12]; + _secureRandom.nextBytes(iv); + System.arraycopy(keyAndIv, 0, key, 0, key.length); + Cipher cipher = Cipher.getInstance(CIPHER_ALG); + GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); + cipher.init(mode, new SecretKeySpec(key, KEY_ALG), gcmSpec); + return cipher; + } +} diff --git a/src/main/java/org/codehaus/plexus/components/cipher/internal/Cipher.java b/src/main/java/org/codehaus/plexus/components/cipher/internal/Cipher.java new file mode 100644 index 0000000..970c53f --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/cipher/internal/Cipher.java @@ -0,0 +1,37 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + */ + +package org.codehaus.plexus.components.cipher.internal; + +import org.codehaus.plexus.components.cipher.PlexusCipherException; + +/** + * Cipher interface. + */ +public interface Cipher { + /** + * Encrypts the clear text data with password and returns result. + */ + String encrypt(final String clearText, final String password) throws PlexusCipherException; + + /** + * Decrypts the encrypted text with password and returns clear text result. + */ + String decrypt(final String encryptedText, final String password) throws PlexusCipherException; +} diff --git a/src/main/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipher.java b/src/main/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipher.java index 328abf5..b56bedc 100644 --- a/src/main/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipher.java +++ b/src/main/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipher.java @@ -12,12 +12,15 @@ */ package org.codehaus.plexus.components.cipher.internal; +import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.security.Provider; import java.security.Security; +import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -25,6 +28,8 @@ import org.codehaus.plexus.components.cipher.PlexusCipher; import org.codehaus.plexus.components.cipher.PlexusCipherException; +import static java.util.Objects.requireNonNull; + /** * Default implementation of {@link PlexusCipher}. This class is thread safe. * @@ -33,72 +38,75 @@ @Singleton @Named public class DefaultPlexusCipher implements PlexusCipher { - private static final Pattern ENCRYPTED_STRING_PATTERN = Pattern.compile(".*?[^\\\\]?\\{(.*?[^\\\\])\\}.*"); + private static final Pattern ENCRYPTED_STRING_PATTERN = Pattern.compile(".*?[^\\\\]?\\{(.*?[^\\\\])}.*"); private static final String ENCRYPTED_STRING_DECORATION_START = "{"; private static final String ENCRYPTED_STRING_DECORATION_STOP = "}"; - private final PBECipher _cipher; + private final Map ciphers; - // --------------------------------------------------------------- - public DefaultPlexusCipher() { - _cipher = new PBECipher(); + @Inject + public DefaultPlexusCipher(Map ciphers) { + this.ciphers = requireNonNull(ciphers); } - // --------------------------------------------------------------- @Override - public String encrypt(final String str, final String passPhrase) throws PlexusCipherException { - if (str == null || str.isEmpty()) { + public Set availableCiphers() { + return Collections.unmodifiableSet(ciphers.keySet()); + } + + @Override + public String encrypt(String alg, String str, String passPhrase) throws PlexusCipherException { + requireNonNull(alg); + requireNonNull(str); + requireNonNull(passPhrase); + if (str.isEmpty()) { return str; } - - return _cipher.encrypt64(str, passPhrase); + return requireCipher(alg).encrypt(str, passPhrase); } - // --------------------------------------------------------------- @Override - public String encryptAndDecorate(final String str, final String passPhrase) throws PlexusCipherException { - return decorate(encrypt(str, passPhrase)); + public String encryptAndDecorate(String alg, String str, String passPhrase) throws PlexusCipherException { + return decorate(encrypt(alg, str, passPhrase)); } - // --------------------------------------------------------------- @Override - public String decrypt(final String str, final String passPhrase) throws PlexusCipherException { - if (str == null || str.isEmpty()) { + public String decrypt(String alg, String str, String passPhrase) throws PlexusCipherException { + requireNonNull(alg); + requireNonNull(str); + requireNonNull(passPhrase); + if (str.isEmpty()) { return str; } - - return _cipher.decrypt64(str, passPhrase); + return requireCipher(alg).decrypt(str, passPhrase); } - // --------------------------------------------------------------- @Override - public String decryptDecorated(final String str, final String passPhrase) throws PlexusCipherException { - if (str == null || str.isEmpty()) { + public String decryptDecorated(String alg, String str, String passPhrase) throws PlexusCipherException { + requireNonNull(alg); + requireNonNull(str); + requireNonNull(passPhrase); + if (str.isEmpty()) { return str; } - if (isEncryptedString(str)) { - return decrypt(unDecorate(str), passPhrase); + str = unDecorate(str); } - - return decrypt(str, passPhrase); + return decrypt(alg, str, passPhrase); } - // ---------------------------------------------------------------------------- @Override - public boolean isEncryptedString(final String str) { + public boolean isEncryptedString(String str) { if (str == null || str.isEmpty()) { return false; } - Matcher matcher = ENCRYPTED_STRING_PATTERN.matcher(str); - return matcher.matches() || matcher.find(); } - // ---------------------------------------------------------------------------- @Override - public String unDecorate(final String str) throws PlexusCipherException { + public String unDecorate(String str) throws PlexusCipherException { + requireNonNull(str); Matcher matcher = ENCRYPTED_STRING_PATTERN.matcher(str); if (matcher.matches() || matcher.find()) { return matcher.group(1); @@ -107,12 +115,19 @@ public String unDecorate(final String str) throws PlexusCipherException { } } - // ---------------------------------------------------------------------------- @Override - public String decorate(final String str) { + public String decorate(String str) { return ENCRYPTED_STRING_DECORATION_START + (str == null ? "" : str) + ENCRYPTED_STRING_DECORATION_STOP; } + private Cipher requireCipher(String alg) throws PlexusCipherException { + Cipher cipher = ciphers.get(alg); + if (cipher == null) { + throw new PlexusCipherException("Unsupported alg: " + alg); + } + return cipher; + } + // --------------------------------------------------------------- /** @@ -166,7 +181,6 @@ public static String[] getCryptoImpls(final String serviceType) { return result.toArray(new String[0]); } - // --------------------------------------------------------------- public static void main(final String[] args) { String[] serviceTypes = getServiceTypes(); for (String serviceType : serviceTypes) { diff --git a/src/test/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5PaddingTest.java b/src/test/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5PaddingTest.java new file mode 100644 index 0000000..a675a50 --- /dev/null +++ b/src/test/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5PaddingTest.java @@ -0,0 +1,8 @@ +package org.codehaus.plexus.components.cipher.internal; + +public class AESCBCPKCS5PaddingTest extends CipherTestSupport { + @Override + Cipher getCipher() { + return new AESCBCPKCS5Padding(); + } +} diff --git a/src/test/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPaddingTest.java b/src/test/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPaddingTest.java new file mode 100644 index 0000000..7d2294b --- /dev/null +++ b/src/test/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPaddingTest.java @@ -0,0 +1,8 @@ +package org.codehaus.plexus.components.cipher.internal; + +public class AESGCMNoPaddingTest extends CipherTestSupport { + @Override + Cipher getCipher() { + return new AESGCMNoPadding(); + } +} diff --git a/src/test/java/org/codehaus/plexus/components/cipher/internal/PBECipherTest.java b/src/test/java/org/codehaus/plexus/components/cipher/internal/CipherTestSupport.java similarity index 73% rename from src/test/java/org/codehaus/plexus/components/cipher/internal/PBECipherTest.java rename to src/test/java/org/codehaus/plexus/components/cipher/internal/CipherTestSupport.java index e263005..6ac820b 100644 --- a/src/test/java/org/codehaus/plexus/components/cipher/internal/PBECipherTest.java +++ b/src/test/java/org/codehaus/plexus/components/cipher/internal/CipherTestSupport.java @@ -19,6 +19,8 @@ Licensed to the Apache Software Foundation (ASF) under one package org.codehaus.plexus.components.cipher.internal; +import java.nio.charset.Charset; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -26,54 +28,43 @@ Licensed to the Apache Software Foundation (ASF) under one import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -/** - * @author Oleg Gusakov - */ -class PBECipherTest { - PBECipher pbeCipher; - +public abstract class CipherTestSupport { final String clearText = "veryOpenText"; - - final String encryptedText = "xnQ1RvJFoJsHoTZKyv76ej3XTGKt99ShUt/kPv4yHjw="; - final String password = "testtest"; + Cipher pbeCipher; + @BeforeEach void prepare() { - pbeCipher = new PBECipher(); + pbeCipher = getCipher(); } + abstract Cipher getCipher(); + @Test void testEncrypt() throws Exception { - String enc = pbeCipher.encrypt64(clearText, password); - + String enc = pbeCipher.encrypt(clearText, password); assertNotNull(enc); - System.out.println(enc); - - String enc2 = pbeCipher.encrypt64(clearText, password); - + String enc2 = pbeCipher.encrypt(clearText, password); assertNotNull(enc2); - System.out.println(enc2); - assertNotEquals(enc, enc2); } @Test void testDecrypt() throws Exception { - String clear = pbeCipher.decrypt64(encryptedText, password); - + String enc = pbeCipher.encrypt(clearText, password); + String clear = pbeCipher.decrypt(enc, password); assertEquals(clearText, clear); } @Test void testEncoding() throws Exception { - System.out.println("file.encoding=" + System.getProperty("file.encoding")); - + System.out.println("file.encoding=" + Charset.defaultCharset().displayName()); String pwd = "äüöÜÖÄß\"§$%&/()=?é"; - String encPwd = pbeCipher.encrypt64(pwd, pwd); - String decPwd = pbeCipher.decrypt64(encPwd, pwd); + String encPwd = pbeCipher.encrypt(pwd, pwd); + String decPwd = pbeCipher.decrypt(encPwd, pwd); assertEquals(pwd, decPwd); } } diff --git a/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java b/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java index 100acda..3e8ca58 100644 --- a/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java +++ b/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java @@ -12,11 +12,16 @@ */ package org.codehaus.plexus.components.cipher.internal; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; + import org.codehaus.plexus.components.cipher.PlexusCipher; import org.codehaus.plexus.components.cipher.PlexusCipherException; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.FieldSource; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,15 +36,23 @@ */ class DefaultPlexusCipherTest { private final String passPhrase = "testtest"; - final String str = "my testing phrase"; - - final String encStr = "RRvejxJ+wksH/kWnYfun/GeFoPKh6JHcA2dmxMOIraZiIuLISplmdyvl2Sq04rpP"; PlexusCipher pc; + static String[] ALG = new String[] {AESCBCPKCS5Padding.CIPHER_ALG, AESGCMNoPadding.CIPHER_ALG}; + @BeforeEach void prepare() { - pc = new DefaultPlexusCipher(); + HashMap ciphers = new HashMap<>(); + ciphers.put(AESCBCPKCS5Padding.CIPHER_ALG, new AESGCMNoPadding()); + ciphers.put(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding()); + pc = new DefaultPlexusCipher(ciphers); + } + + @Test + void testAvailableCiphers() { + HashSet wanted = new HashSet<>(Arrays.asList(ALG)); + assertEquals(wanted, pc.availableCiphers()); } @Test @@ -48,13 +61,9 @@ void testIsEncryptedString() { String normalBraces = "Comment {This is a test} other comment with a: }"; String escapedBraces = "\\{This is a test\\}"; String mixedBraces = "Comment {foo\\{This is a test\\}} other comment with a: }"; - assertFalse(pc.isEncryptedString(noBraces)); - assertTrue(pc.isEncryptedString(normalBraces)); - assertFalse(pc.isEncryptedString(escapedBraces)); - assertTrue(pc.isEncryptedString(mixedBraces)); } @@ -63,116 +72,79 @@ void testUnDecorate_BracesPermutations() throws PlexusCipherException { String noBraces = "This is a test"; String normalBraces = "Comment {This is a test} other comment with a: }"; String mixedBraces = "Comment {foo\\{This is a test\\}} other comment with a: }"; - assertEquals(noBraces, pc.unDecorate(normalBraces)); - assertEquals("foo\\{" + noBraces + "\\}", pc.unDecorate(mixedBraces)); } - // ------------------------------------------------------------- - @Test - void testDefaultAlgorithmExists() throws Exception { + void testAllAlgorithmExists() throws Exception { String[] res = DefaultPlexusCipher.getCryptoImpls("Cipher"); assertNotNull(res, "No Cipher providers found in the current environment"); - - System.out.println("\n=== Available ciphers :"); - for (String re : res) { - System.out.println(re); - } - System.out.println("===================="); - + // System.out.println("\n=== Available ciphers :"); + // for (String re : res) { + // System.out.println(re); + // } + // System.out.println("===================="); + HashSet algs = new HashSet<>(pc.availableCiphers()); for (String provider : res) { - if (PBECipher.KEY_ALG.equalsIgnoreCase(provider)) return; + algs.remove(provider); + } + if (!algs.isEmpty()) { + throw new Exception("Cannot find algorithms " + algs + " in the current environment."); } - - throw new Exception("Cannot find default algorithm " + PBECipher.KEY_ALG + " in the current environment."); - } - - // ------------------------------------------------------------- - - @Disabled("This test is not really a test") - @Test - void stestFindDefaultAlgorithm() { - String[] res = DefaultPlexusCipher.getServiceTypes(); - assertNotNull(res, "No service types found in the current environment"); - - String[] impls = DefaultPlexusCipher.getCryptoImpls("Cipher"); - assertNotNull(impls, "No Cipher providers found in the current environment"); - - for (String impl : impls) - try { - System.out.print(impl); - pc.encrypt(str, passPhrase); - System.out.println("------------------> Success !!!!!!"); - } catch (Exception e) { - System.out.println(e.getMessage()); - } } - // ------------------------------------------------------------- - @Test - void testEncrypt() throws Exception { - String xRes = pc.encrypt(str, passPhrase); - - System.out.println(xRes); - - String res = pc.decrypt(xRes, passPhrase); - + @ParameterizedTest + @FieldSource("ALG") + void testEncrypt(String alg) throws Exception { + String xRes = pc.encrypt(alg, str, passPhrase); + // System.out.println(xRes); + String res = pc.decrypt(alg, xRes, passPhrase); assertEquals(str, res, "Encryption/Decryption did not produce desired result"); } - // ------------------------------------------------------------- - - @Test - void testEncryptVariableLengths() throws Exception { + @ParameterizedTest + @FieldSource("ALG") + void testEncryptVariableLengths(String alg) throws Exception { String pass = "g"; - for (int i = 0; i < 64; i++) { pass = pass + 'a'; - - String xRes = pc.encrypt(str, pass); - - System.out.println(pass.length() + ": " + xRes); - - String res = pc.decrypt(xRes, pass); - + String xRes = pc.encrypt(alg, str, pass); + // System.out.println(pass.length() + ": " + xRes); + String res = pc.decrypt(alg, xRes, pass); assertEquals(str, res, "Encryption/Decryption did not produce desired result"); } } - @Test - void testDecrypt() { + @ParameterizedTest + @FieldSource("ALG") + void testDecrypt(String alg) { assertDoesNotThrow( () -> { - String res = pc.decrypt(encStr, passPhrase); + String encStr = pc.encrypt(alg, str, passPhrase); + String res = pc.decrypt(alg, encStr, passPhrase); assertEquals(str, res, "Decryption did not produce desired result"); }, "Decryption failed: "); } - // ------------------------------------------------------------- - @Test void testDecorate() { String res = pc.decorate("aaa"); assertEquals("{aaa}", res, "Decoration failed"); } - // ------------------------------------------------------------- - @Test void testUnDecorate() throws Exception { String res = pc.unDecorate("{aaa}"); assertEquals("aaa", res, "Decoration failed"); } - // ------------------------------------------------------------- - - @Test - void testEncryptAndDecorate() throws Exception { - String res = pc.encryptAndDecorate("my-password", "12345678"); - + @ParameterizedTest + @FieldSource("ALG") + void testEncryptAndDecorate(String alg) throws Exception { + String res = pc.encryptAndDecorate(alg, "my-password", "12345678"); assertEquals('{', res.charAt(0)); + assertEquals('}', res.charAt(res.length() - 1)); } } From 5d651e13a3ae0d28562390c5a7393972095ba1f2 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 19:26:54 +0200 Subject: [PATCH 2/6] Updates --- .../cipher/internal/AESGCMNoPadding.java | 98 +++++++++---------- .../internal/DefaultPlexusCipherTest.java | 3 + 2 files changed, 49 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java b/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java index 59a28c4..e0c9c82 100644 --- a/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java +++ b/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java @@ -20,7 +20,7 @@ Licensed to the Apache Software Foundation (ASF) under one package org.codehaus.plexus.components.cipher.internal; import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.PBEKeySpec; @@ -28,9 +28,8 @@ Licensed to the Apache Software Foundation (ASF) under one import javax.inject.Named; import javax.inject.Singleton; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; @@ -44,70 +43,65 @@ Licensed to the Apache Software Foundation (ASF) under one public class AESGCMNoPadding implements org.codehaus.plexus.components.cipher.internal.Cipher { public static final String CIPHER_ALG = "AES/GCM/NoPadding"; - private static final int SPICE_SIZE = 16; - private static final int SALT_SIZE = 8; - private static final int CHUNK_SIZE = 16; - private static final String KEY_ALG = "AES"; + private static final int TAG_LENGTH_BIT = 128; + private static final int IV_LENGTH_BYTE = 12; + private static final int SALT_LENGTH_BYTE = 16; private static final int PBE_ITERATIONS = 310000; - private static final SecureRandom _secureRandom = new SecureRandom(); - - private byte[] getSalt(int sz) { - byte[] res = new byte[sz]; - _secureRandom.nextBytes(res); - return res; - } + private static final int PBE_KEY_SIZE = SALT_LENGTH_BYTE * 16; + private static final String KEY_FACTORY = "PBKDF2WithHmacSHA512"; + private static final String KEY_ALGORITHM = "AES"; @Override public String encrypt(String clearText, String password) throws PlexusCipherException { try { - byte[] clearBytes = clearText.getBytes(StandardCharsets.UTF_8); - byte[] salt = getSalt(SALT_SIZE); - Cipher cipher = createCipher(password.toCharArray(), salt, Cipher.ENCRYPT_MODE); - byte[] encryptedBytes = cipher.doFinal(clearBytes); - int len = encryptedBytes.length; - byte padLen = (byte) (CHUNK_SIZE - (SALT_SIZE + len + 1) % CHUNK_SIZE); - int totalLen = SALT_SIZE + len + padLen + 1; - byte[] allEncryptedBytes = getSalt(totalLen); - System.arraycopy(salt, 0, allEncryptedBytes, 0, SALT_SIZE); - allEncryptedBytes[SALT_SIZE] = padLen; - System.arraycopy(encryptedBytes, 0, allEncryptedBytes, SALT_SIZE + 1, len); - return Base64.getEncoder().encodeToString(allEncryptedBytes); + byte[] salt = getRandomNonce(SALT_LENGTH_BYTE); + byte[] iv = getRandomNonce(IV_LENGTH_BYTE); + SecretKey secretKey = getAESKeyFromPassword(password.toCharArray(), salt); + Cipher cipher = Cipher.getInstance(CIPHER_ALG); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); + byte[] cipherText = cipher.doFinal(clearText.getBytes(StandardCharsets.UTF_8)); + byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length) + .put(iv) + .put(salt) + .put(cipherText) + .array(); + return Base64.getEncoder().encodeToString(cipherTextWithIvSalt); } catch (Exception e) { - throw new PlexusCipherException(e.getMessage(), e); + throw new PlexusCipherException("Failed encrypting", e); } } @Override public String decrypt(String encryptedText, String password) throws PlexusCipherException { try { - byte[] allEncryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes()); - int totalLen = allEncryptedBytes.length; - byte[] salt = new byte[SALT_SIZE]; - System.arraycopy(allEncryptedBytes, 0, salt, 0, SALT_SIZE); - byte padLen = allEncryptedBytes[SALT_SIZE]; - byte[] encryptedBytes = new byte[totalLen - SALT_SIZE - 1 - padLen]; - System.arraycopy(allEncryptedBytes, SALT_SIZE + 1, encryptedBytes, 0, encryptedBytes.length); - Cipher cipher = createCipher(password.toCharArray(), salt, Cipher.DECRYPT_MODE); - byte[] clearBytes = cipher.doFinal(encryptedBytes); - return new String(clearBytes, StandardCharsets.UTF_8); + byte[] material = Base64.getDecoder().decode(encryptedText.getBytes(StandardCharsets.UTF_8)); + ByteBuffer buffer = ByteBuffer.wrap(material); + byte[] iv = new byte[IV_LENGTH_BYTE]; + buffer.get(iv); + byte[] salt = new byte[SALT_LENGTH_BYTE]; + buffer.get(salt); + byte[] cipherText = new byte[buffer.remaining()]; + buffer.get(cipherText); + SecretKey secretKey = getAESKeyFromPassword(password.toCharArray(), salt); + Cipher cipher = Cipher.getInstance(CIPHER_ALG); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); + byte[] plainText = cipher.doFinal(cipherText); + return new String(plainText, StandardCharsets.UTF_8); } catch (Exception e) { - throw new PlexusCipherException(e.getMessage(), e); + throw new PlexusCipherException("Failed decrypting", e); } } - private Cipher createCipher(char[] pwd, byte[] salt, int mode) - throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, - InvalidAlgorithmParameterException, InvalidKeySpecException { - KeySpec spec = new PBEKeySpec(pwd, salt, PBE_ITERATIONS, SPICE_SIZE * 16); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); - byte[] keyAndIv = factory.generateSecret(spec).getEncoded(); - byte[] key = new byte[SPICE_SIZE]; - byte[] iv = new byte[12]; - _secureRandom.nextBytes(iv); - System.arraycopy(keyAndIv, 0, key, 0, key.length); - Cipher cipher = Cipher.getInstance(CIPHER_ALG); - GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); - cipher.init(mode, new SecretKeySpec(key, KEY_ALG), gcmSpec); - return cipher; + public static byte[] getRandomNonce(int numBytes) throws NoSuchAlgorithmException { + byte[] nonce = new byte[numBytes]; + SecureRandom.getInstanceStrong().nextBytes(nonce); + return nonce; + } + + public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt) + throws NoSuchAlgorithmException, InvalidKeySpecException { + SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_FACTORY); + KeySpec spec = new PBEKeySpec(password, salt, PBE_ITERATIONS, PBE_KEY_SIZE); + return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), KEY_ALGORITHM); } } diff --git a/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java b/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java index 3e8ca58..14b9627 100644 --- a/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java +++ b/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java @@ -86,6 +86,9 @@ void testAllAlgorithmExists() throws Exception { // } // System.out.println("===================="); HashSet algs = new HashSet<>(pc.availableCiphers()); + // TODO: seems this alg is not offered by default? + algs.remove(AESCBCPKCS5Padding.CIPHER_ALG); + for (String provider : res) { algs.remove(provider); } From be21cc8fc182f6cc2e2971e169bb8292cd46f716 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 19:33:02 +0200 Subject: [PATCH 3/6] Remove wfl --- .github/workflows/codeql-analysis.yml | 38 --------------------------- 1 file changed, 38 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 32cbb59..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,38 +0,0 @@ - -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - workflow_dispatch: - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: 'java' - - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - - From f011b27bcc8d5da76d932577c529ea573cecf904 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 19:37:09 +0200 Subject: [PATCH 4/6] Drop AESCBCPKCS5Padding --- .../cipher/internal/AESCBCPKCS5Padding.java | 112 ------------------ .../internal/AESCBCPKCS5PaddingTest.java | 8 -- .../internal/DefaultPlexusCipherTest.java | 6 +- 3 files changed, 1 insertion(+), 125 deletions(-) delete mode 100644 src/main/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5Padding.java delete mode 100644 src/test/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5PaddingTest.java diff --git a/src/main/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5Padding.java b/src/main/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5Padding.java deleted file mode 100644 index 9b6fd97..0000000 --- a/src/main/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5Padding.java +++ /dev/null @@ -1,112 +0,0 @@ -/* -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. - */ - -package org.codehaus.plexus.components.cipher.internal; - -import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; -import javax.inject.Named; -import javax.inject.Singleton; - -import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; -import java.util.Base64; - -import org.codehaus.plexus.components.cipher.PlexusCipherException; - -@Singleton -@Named(AESCBCPKCS5Padding.CIPHER_ALG) -public class AESCBCPKCS5Padding implements org.codehaus.plexus.components.cipher.internal.Cipher { - public static final String CIPHER_ALG = "AES/CBC/PKCS5Padding"; - - private static final int SPICE_SIZE = 16; - private static final int SALT_SIZE = 8; - private static final int CHUNK_SIZE = 16; - private static final String KEY_ALG = "AES"; - private static final int PBE_ITERATIONS = 310000; - private static final SecureRandom _secureRandom = new SecureRandom(); - - private byte[] getSalt(int sz) { - byte[] res = new byte[sz]; - _secureRandom.nextBytes(res); - return res; - } - - @Override - public String encrypt(String clearText, String password) throws PlexusCipherException { - try { - byte[] clearBytes = clearText.getBytes(StandardCharsets.UTF_8); - byte[] salt = getSalt(SALT_SIZE); - Cipher cipher = createCipher(password.toCharArray(), salt, Cipher.ENCRYPT_MODE); - byte[] encryptedBytes = cipher.doFinal(clearBytes); - int len = encryptedBytes.length; - byte padLen = (byte) (CHUNK_SIZE - (SALT_SIZE + len + 1) % CHUNK_SIZE); - int totalLen = SALT_SIZE + len + padLen + 1; - byte[] allEncryptedBytes = getSalt(totalLen); - System.arraycopy(salt, 0, allEncryptedBytes, 0, SALT_SIZE); - allEncryptedBytes[SALT_SIZE] = padLen; - System.arraycopy(encryptedBytes, 0, allEncryptedBytes, SALT_SIZE + 1, len); - return Base64.getEncoder().encodeToString(allEncryptedBytes); - } catch (Exception e) { - throw new PlexusCipherException(e.getMessage(), e); - } - } - - @Override - public String decrypt(String encryptedText, String password) throws PlexusCipherException { - try { - byte[] allEncryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes()); - int totalLen = allEncryptedBytes.length; - byte[] salt = new byte[SALT_SIZE]; - System.arraycopy(allEncryptedBytes, 0, salt, 0, SALT_SIZE); - byte padLen = allEncryptedBytes[SALT_SIZE]; - byte[] encryptedBytes = new byte[totalLen - SALT_SIZE - 1 - padLen]; - System.arraycopy(allEncryptedBytes, SALT_SIZE + 1, encryptedBytes, 0, encryptedBytes.length); - Cipher cipher = createCipher(password.toCharArray(), salt, Cipher.DECRYPT_MODE); - byte[] clearBytes = cipher.doFinal(encryptedBytes); - return new String(clearBytes, StandardCharsets.UTF_8); - } catch (Exception e) { - throw new PlexusCipherException(e.getMessage(), e); - } - } - - private Cipher createCipher(char[] pwd, byte[] salt, int mode) - throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, - InvalidAlgorithmParameterException, InvalidKeySpecException { - KeySpec spec = new PBEKeySpec(pwd, salt, PBE_ITERATIONS, SPICE_SIZE * 16); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); - byte[] keyAndIv = factory.generateSecret(spec).getEncoded(); - byte[] key = new byte[SPICE_SIZE]; - byte[] iv = new byte[SPICE_SIZE]; - System.arraycopy(keyAndIv, 0, key, 0, key.length); - System.arraycopy(keyAndIv, key.length, iv, 0, iv.length); - Cipher cipher = Cipher.getInstance(CIPHER_ALG); - cipher.init(mode, new SecretKeySpec(key, KEY_ALG), new IvParameterSpec(iv)); - return cipher; - } -} diff --git a/src/test/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5PaddingTest.java b/src/test/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5PaddingTest.java deleted file mode 100644 index a675a50..0000000 --- a/src/test/java/org/codehaus/plexus/components/cipher/internal/AESCBCPKCS5PaddingTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.codehaus.plexus.components.cipher.internal; - -public class AESCBCPKCS5PaddingTest extends CipherTestSupport { - @Override - Cipher getCipher() { - return new AESCBCPKCS5Padding(); - } -} diff --git a/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java b/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java index 14b9627..94c16ca 100644 --- a/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java +++ b/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java @@ -39,12 +39,11 @@ class DefaultPlexusCipherTest { final String str = "my testing phrase"; PlexusCipher pc; - static String[] ALG = new String[] {AESCBCPKCS5Padding.CIPHER_ALG, AESGCMNoPadding.CIPHER_ALG}; + static String[] ALG = new String[] {AESGCMNoPadding.CIPHER_ALG}; @BeforeEach void prepare() { HashMap ciphers = new HashMap<>(); - ciphers.put(AESCBCPKCS5Padding.CIPHER_ALG, new AESGCMNoPadding()); ciphers.put(AESGCMNoPadding.CIPHER_ALG, new AESGCMNoPadding()); pc = new DefaultPlexusCipher(ciphers); } @@ -86,9 +85,6 @@ void testAllAlgorithmExists() throws Exception { // } // System.out.println("===================="); HashSet algs = new HashSet<>(pc.availableCiphers()); - // TODO: seems this alg is not offered by default? - algs.remove(AESCBCPKCS5Padding.CIPHER_ALG); - for (String provider : res) { algs.remove(provider); } From ce313ba0d87db3867f5a35a52b85844fa26cf10a Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 19:40:25 +0200 Subject: [PATCH 5/6] Helpers are private --- .../plexus/components/cipher/internal/AESGCMNoPadding.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java b/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java index e0c9c82..e9b1e32 100644 --- a/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java +++ b/src/main/java/org/codehaus/plexus/components/cipher/internal/AESGCMNoPadding.java @@ -92,13 +92,13 @@ public String decrypt(String encryptedText, String password) throws PlexusCipher } } - public static byte[] getRandomNonce(int numBytes) throws NoSuchAlgorithmException { + private static byte[] getRandomNonce(int numBytes) throws NoSuchAlgorithmException { byte[] nonce = new byte[numBytes]; SecureRandom.getInstanceStrong().nextBytes(nonce); return nonce; } - public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt) + private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_FACTORY); KeySpec spec = new PBEKeySpec(password, salt, PBE_ITERATIONS, PBE_KEY_SIZE); From 00400c74ba6e61464537847b127b7750cd9712e6 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 19:49:32 +0200 Subject: [PATCH 6/6] Remove this test, it is Java version specific --- .../internal/DefaultPlexusCipherTest.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java b/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java index 94c16ca..46f1c2c 100644 --- a/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java +++ b/src/test/java/org/codehaus/plexus/components/cipher/internal/DefaultPlexusCipherTest.java @@ -26,7 +26,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -75,24 +74,6 @@ void testUnDecorate_BracesPermutations() throws PlexusCipherException { assertEquals("foo\\{" + noBraces + "\\}", pc.unDecorate(mixedBraces)); } - @Test - void testAllAlgorithmExists() throws Exception { - String[] res = DefaultPlexusCipher.getCryptoImpls("Cipher"); - assertNotNull(res, "No Cipher providers found in the current environment"); - // System.out.println("\n=== Available ciphers :"); - // for (String re : res) { - // System.out.println(re); - // } - // System.out.println("===================="); - HashSet algs = new HashSet<>(pc.availableCiphers()); - for (String provider : res) { - algs.remove(provider); - } - if (!algs.isEmpty()) { - throw new Exception("Cannot find algorithms " + algs + " in the current environment."); - } - } - @ParameterizedTest @FieldSource("ALG") void testEncrypt(String alg) throws Exception {