Deprecate AesBytesEncryptor
This commit separates AesBytesEncryptor into two separate implememtations, allowing for a migration away from default arrangements that used a null IV Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
This commit is contained in:
+2
@@ -37,7 +37,9 @@ import org.springframework.security.crypto.util.EncodingUtils;
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Dave Syer
|
||||
* @deprecated Use {@link AesCbcBytesEncryptor} or {@link AesGcmBytesEncryptor} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class AesBytesEncryptor implements BytesEncryptor {
|
||||
|
||||
private final SecretKey secretKey;
|
||||
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.security.crypto.encrypt;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.security.crypto.util.EncodingUtils;
|
||||
|
||||
/**
|
||||
* {@link BytesEncryptor} that uses 256-bit AES/CBC/PKCS5Padding with a random 16-byte
|
||||
* initialization vector. The IV is prepended to the ciphertext on encrypt and stripped on
|
||||
* decrypt.
|
||||
*
|
||||
* <p>
|
||||
* Note that CBC mode provides confidentiality but <em>not</em> integrity or authenticity.
|
||||
* Applications that require authenticated encryption should prefer
|
||||
* {@link AesGcmBytesEncryptor}. See the <a href=
|
||||
* "https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html">
|
||||
* OWASP Cryptographic Storage Cheat Sheet</a> for guidance on choosing a cipher mode.
|
||||
*
|
||||
* <p>
|
||||
* When key derivation is used via {@link #withPassword(String, CharSequence)}, the key is
|
||||
* derived using PBKDF2WithHmacSHA256 with {@code DEFAULT_PBKDF2_ITERATIONS} iterations
|
||||
* per the <a href=
|
||||
* "https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html">
|
||||
* OWASP Password Storage Cheat Sheet</a>. Because derivation is intentionally expensive,
|
||||
* the encryptor instance should be created once and reused rather than constructed
|
||||
* per-operation.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.7.26
|
||||
* @see AesGcmBytesEncryptor
|
||||
* @see AesBytesEncryptor
|
||||
*/
|
||||
public final class AesCbcBytesEncryptor implements BytesEncryptor {
|
||||
|
||||
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
|
||||
|
||||
private static final int IV_LENGTH_BYTES = 16;
|
||||
|
||||
private static final int DEFAULT_PBKDF2_ITERATIONS = 600_000;
|
||||
|
||||
private final SecretKey secretKey;
|
||||
|
||||
private final Cipher encryptor;
|
||||
|
||||
private final Cipher decryptor;
|
||||
|
||||
private final BytesKeyGenerator ivGenerator;
|
||||
|
||||
private AesCbcBytesEncryptor(SecretKey secretKey, BytesKeyGenerator ivGenerator) {
|
||||
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
|
||||
this.encryptor = CipherUtils.newCipher(ALGORITHM);
|
||||
this.decryptor = CipherUtils.newCipher(ALGORITHM);
|
||||
this.ivGenerator = ivGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an encryptor that derives its key from the given password and hex-encoded
|
||||
* salt using PBKDF2WithHmacSHA1.
|
||||
* @param password the password value
|
||||
* @param salt the hex-encoded salt value
|
||||
*/
|
||||
public static Builder withPassword(String password, CharSequence salt) {
|
||||
return new Builder(password, salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an encryptor using the supplied {@link SecretKey}.
|
||||
* @param secretKey the secret (symmetric) key
|
||||
*/
|
||||
public static Builder withSecretKey(SecretKey secretKey) {
|
||||
return new Builder(secretKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encrypt(byte[] bytes) {
|
||||
synchronized (this.encryptor) {
|
||||
byte[] iv = this.ivGenerator.generateKey();
|
||||
CipherUtils.initCipher(this.encryptor, Cipher.ENCRYPT_MODE, this.secretKey, new IvParameterSpec(iv));
|
||||
byte[] ciphertext = CipherUtils.doFinal(this.encryptor, bytes);
|
||||
return EncodingUtils.concatenate(iv, ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] decrypt(byte[] encryptedBytes) {
|
||||
int ivLength = this.ivGenerator.getKeyLength();
|
||||
byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, ivLength);
|
||||
byte[] ciphertext = EncodingUtils.subArray(encryptedBytes, ivLength, encryptedBytes.length);
|
||||
synchronized (this.decryptor) {
|
||||
CipherUtils.initCipher(this.decryptor, Cipher.DECRYPT_MODE, this.secretKey, new IvParameterSpec(iv));
|
||||
return CipherUtils.doFinal(this.decryptor, ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
private static SecretKey deriveKey(String password, CharSequence salt) {
|
||||
return CipherUtils.newSecretKey("PBKDF2WithHmacSHA256",
|
||||
new PBEKeySpec(password.toCharArray(), Hex.decode(salt), DEFAULT_PBKDF2_ITERATIONS, 256));
|
||||
}
|
||||
|
||||
/**
|
||||
* A Builder for {@link AesCbcBytesEncryptor}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final SecretKey secretKey;
|
||||
|
||||
private BytesKeyGenerator ivGenerator = KeyGenerators.secureRandom(IV_LENGTH_BYTES);
|
||||
|
||||
private Builder(SecretKey secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
private Builder(String password, CharSequence salt) {
|
||||
this.secretKey = deriveKey(password, salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link BytesKeyGenerator} to use for generating the initialization
|
||||
* vector.
|
||||
* @param ivGenerator the {@link BytesKeyGenerator} to use for generating the
|
||||
* initialization vector
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder ivGenerator(BytesKeyGenerator ivGenerator) {
|
||||
Objects.requireNonNull(ivGenerator, "ivGenerator cannot be null");
|
||||
this.ivGenerator = ivGenerator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link AesCbcBytesEncryptor}.
|
||||
* @return the {@link AesCbcBytesEncryptor}
|
||||
*/
|
||||
public AesCbcBytesEncryptor build() {
|
||||
return new AesCbcBytesEncryptor(this.secretKey, this.ivGenerator);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.security.crypto.encrypt;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.security.crypto.util.EncodingUtils;
|
||||
|
||||
/**
|
||||
* {@link BytesEncryptor} that uses 256-bit AES/GCM/NoPadding with a random 16-byte
|
||||
* initialization vector and a 128-bit authentication tag. The IV is prepended to the
|
||||
* ciphertext on encrypt and stripped on decrypt. GCM provides authenticated encryption
|
||||
* (AEAD): both confidentiality and integrity are protected, and decryption throws if the
|
||||
* ciphertext has been tampered with.
|
||||
*
|
||||
* <p>
|
||||
* This class uses a 16-byte (128-bit) IV rather than the 12-byte (96-bit) IV recommended
|
||||
* by NIST SP 800-38D for GCM. Both lengths are cryptographically valid; the 16-byte
|
||||
* choice maintains consistency with the rest of the Spring Security crypto module. For
|
||||
* additional guidance, please see the <a href=
|
||||
* "https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html">
|
||||
* OWASP Cryptographic Storage Cheat Sheet</a>.
|
||||
*
|
||||
* <p>
|
||||
* When key derivation is used via {@link #withPassword(String, CharSequence)}, the key is
|
||||
* derived using PBKDF2WithHmacSHA256 with {@code DEFAULT_PBKDF2_ITERATIONS} iterations
|
||||
* per the <a href=
|
||||
* "https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html">
|
||||
* OWASP Password Storage Cheat Sheet</a>. Because derivation is intentionally expensive,
|
||||
* the encryptor instance should be created once and reused rather than constructed
|
||||
* per-operation.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.7.26
|
||||
* @see AesCbcBytesEncryptor
|
||||
* @see AesBytesEncryptor
|
||||
*/
|
||||
public final class AesGcmBytesEncryptor implements BytesEncryptor {
|
||||
|
||||
private static final String ALGORITHM = "AES/GCM/NoPadding";
|
||||
|
||||
private static final int IV_LENGTH_BYTES = 16;
|
||||
|
||||
private static final int TAG_LENGTH_BITS = 128;
|
||||
|
||||
private static final int DEFAULT_PBKDF2_ITERATIONS = 600_000;
|
||||
|
||||
private final SecretKey secretKey;
|
||||
|
||||
private final Cipher encryptor;
|
||||
|
||||
private final Cipher decryptor;
|
||||
|
||||
private final BytesKeyGenerator ivGenerator;
|
||||
|
||||
private AesGcmBytesEncryptor(SecretKey secretKey, BytesKeyGenerator ivGenerator) {
|
||||
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
|
||||
this.encryptor = CipherUtils.newCipher(ALGORITHM);
|
||||
this.decryptor = CipherUtils.newCipher(ALGORITHM);
|
||||
this.ivGenerator = ivGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an encryptor that derives its key from the given password and hex-encoded
|
||||
* salt using PBKDF2WithHmacSHA1.
|
||||
* @param password the password value
|
||||
* @param salt the hex-encoded salt value
|
||||
*/
|
||||
public static Builder withPassword(String password, CharSequence salt) {
|
||||
return new Builder(password, salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an encryptor using the supplied {@link SecretKey}.
|
||||
* @param secretKey the secret (symmetric) key
|
||||
*/
|
||||
public static Builder withSecretKey(SecretKey secretKey) {
|
||||
return new Builder(secretKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encrypt(byte[] bytes) {
|
||||
synchronized (this.encryptor) {
|
||||
byte[] iv = this.ivGenerator.generateKey();
|
||||
CipherUtils.initCipher(this.encryptor, Cipher.ENCRYPT_MODE, this.secretKey,
|
||||
new GCMParameterSpec(TAG_LENGTH_BITS, iv));
|
||||
byte[] ciphertext = CipherUtils.doFinal(this.encryptor, bytes);
|
||||
return EncodingUtils.concatenate(iv, ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] decrypt(byte[] encryptedBytes) {
|
||||
int ivLength = this.ivGenerator.getKeyLength();
|
||||
byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, ivLength);
|
||||
byte[] ciphertext = EncodingUtils.subArray(encryptedBytes, ivLength, encryptedBytes.length);
|
||||
synchronized (this.decryptor) {
|
||||
CipherUtils.initCipher(this.decryptor, Cipher.DECRYPT_MODE, this.secretKey,
|
||||
new GCMParameterSpec(TAG_LENGTH_BITS, iv));
|
||||
return CipherUtils.doFinal(this.decryptor, ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
private static SecretKey deriveKey(String password, CharSequence salt) {
|
||||
return CipherUtils.newSecretKey("PBKDF2WithHmacSHA256",
|
||||
new PBEKeySpec(password.toCharArray(), Hex.decode(salt), DEFAULT_PBKDF2_ITERATIONS, 256));
|
||||
}
|
||||
|
||||
/**
|
||||
* A Builder for {@link AesGcmBytesEncryptor}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final SecretKey secretKey;
|
||||
|
||||
private BytesKeyGenerator ivGenerator = KeyGenerators.secureRandom(IV_LENGTH_BYTES);
|
||||
|
||||
private Builder(SecretKey secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
private Builder(String password, CharSequence salt) {
|
||||
this.secretKey = deriveKey(password, salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link BytesKeyGenerator} to use for generating the initialization
|
||||
* vector.
|
||||
* @param ivGenerator the {@link BytesKeyGenerator} to use for generating the
|
||||
* initialization vector
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder ivGenerator(BytesKeyGenerator ivGenerator) {
|
||||
Objects.requireNonNull(ivGenerator, "ivGenerator cannot be null");
|
||||
this.ivGenerator = ivGenerator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link AesGcmBytesEncryptor}.
|
||||
* @return the {@link AesGcmBytesEncryptor}
|
||||
*/
|
||||
public AesGcmBytesEncryptor build() {
|
||||
return new AesGcmBytesEncryptor(this.secretKey, this.ivGenerator);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.security.crypto.encrypt;
|
||||
|
||||
import org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
|
||||
/**
|
||||
@@ -42,9 +41,14 @@ public final class Encryptors {
|
||||
* not be shared
|
||||
* @param salt a hex-encoded, random, site-global salt value to use to generate the
|
||||
* key
|
||||
* @deprecated Use {@link AesGcmBytesEncryptor#withPassword(String, CharSequence)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
public static BytesEncryptor stronger(CharSequence password, CharSequence salt) {
|
||||
return new AesBytesEncryptor(password.toString(), salt, KeyGenerators.secureRandom(16), CipherAlgorithm.GCM);
|
||||
return new AesBytesEncryptor(password.toString(), salt, KeyGenerators.secureRandom(16),
|
||||
AesBytesEncryptor.CipherAlgorithm.GCM);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,18 +57,16 @@ public final class Encryptors {
|
||||
* Function #2). Salts the password to prevent dictionary attacks against the key. The
|
||||
* provided salt is expected to be hex-encoded; it should be random and at least 8
|
||||
* bytes in length. Also applies a random 16-byte initialization vector to ensure each
|
||||
* encrypted message will be unique. Requires Java 6. NOTE: This mode is not
|
||||
* <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">authenticated</a>
|
||||
* and does not provide any guarantees about the authenticity of the data. For a more
|
||||
* secure alternative, users should prefer
|
||||
* {@link #stronger(CharSequence, CharSequence)}.
|
||||
* encrypted message will be unique. Requires Java 6.
|
||||
* @param password the password used to generate the encryptor's secret key; should
|
||||
* not be shared
|
||||
* @param salt a hex-encoded, random, site-global salt value to use to generate the
|
||||
* key
|
||||
*
|
||||
* @see Encryptors#stronger(CharSequence, CharSequence)
|
||||
* @deprecated Use {@link AesCbcBytesEncryptor#withPassword(String, CharSequence)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
|
||||
return new AesBytesEncryptor(password.toString(), salt, KeyGenerators.secureRandom(16));
|
||||
}
|
||||
@@ -74,8 +76,12 @@ public final class Encryptors {
|
||||
* text is hex-encoded.
|
||||
* @param password the password used to generate the encryptor's secret key; should
|
||||
* not be shared
|
||||
* @deprecated Use {@link AesGcmBytesEncryptor#withPassword(String, CharSequence)}
|
||||
* instead.
|
||||
* @see Encryptors#stronger(CharSequence, CharSequence)
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
public static TextEncryptor delux(CharSequence password, CharSequence salt) {
|
||||
return new HexEncodingTextEncryptor(stronger(password, salt));
|
||||
}
|
||||
@@ -85,8 +91,12 @@ public final class Encryptors {
|
||||
* text is hex-encoded.
|
||||
* @param password the password used to generate the encryptor's secret key; should
|
||||
* not be shared
|
||||
* @deprecated Use {@link AesCbcBytesEncryptor#withPassword(String, CharSequence)}
|
||||
* instead.
|
||||
* @see Encryptors#standard(CharSequence, CharSequence)
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
public static TextEncryptor text(CharSequence password, CharSequence salt) {
|
||||
return new HexEncodingTextEncryptor(standard(password, salt));
|
||||
}
|
||||
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.security.crypto.encrypt;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm;
|
||||
import org.springframework.security.crypto.util.EncodingUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AesCbcBytesEncryptor}.
|
||||
*/
|
||||
class AesCbcBytesEncryptorTests {
|
||||
|
||||
private final String secret = "value";
|
||||
|
||||
private final String password = "password";
|
||||
|
||||
private final String hexSalt = "deadbeef";
|
||||
|
||||
@Test
|
||||
void roundtripWhenUsingPasswordAndSaltThenEncryptsAndDecrypts() {
|
||||
CryptoAssumptions.assumeCBCJCE();
|
||||
AesCbcBytesEncryptor encryptor = AesCbcBytesEncryptor.withPassword(this.password, this.hexSalt).build();
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
void roundtripWhenUsingSecretKeyThenEncryptsAndDecrypts() {
|
||||
CryptoAssumptions.assumeCBCJCE();
|
||||
PBEKeySpec keySpec = new PBEKeySpec(this.password.toCharArray(), Hex.decode(this.hexSalt), 1024, 256);
|
||||
SecretKey secretKey = CipherUtils.newSecretKey(SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name(), keySpec);
|
||||
AesCbcBytesEncryptor encryptor = AesCbcBytesEncryptor.withSecretKey(secretKey).build();
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encryptWhenUsingMockIvThenProducesKnownCiphertext() {
|
||||
CryptoAssumptions.assumeCBCJCE();
|
||||
BytesKeyGenerator mockGenerator = mock(BytesKeyGenerator.class);
|
||||
given(mockGenerator.generateKey()).willReturn(Hex.decode("4b0febebd439db7ca77153cb254520c3"));
|
||||
given(mockGenerator.getKeyLength()).willReturn(16);
|
||||
AesCbcBytesEncryptor encryptor = AesCbcBytesEncryptor.withPassword(this.password, this.hexSalt)
|
||||
.ivGenerator(mockGenerator)
|
||||
.build();
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(Hex.encode(encrypted)).isEqualTo("4b0febebd439db7ca77153cb254520c3b7232ac29355d07869433f1ecf55fe94");
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encryptProducesUniqueOutputAndIvIsPrePended() {
|
||||
CryptoAssumptions.assumeCBCJCE();
|
||||
AesCbcBytesEncryptor encryptor = AesCbcBytesEncryptor.withPassword(this.password, this.hexSalt).build();
|
||||
byte[] first = encryptor.encrypt(this.secret.getBytes());
|
||||
byte[] second = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(first).isNotEqualTo(second);
|
||||
assertThat(first.length).isGreaterThan(16);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void migratesFromDeprecatedNullIvCbcToAesCbcBytesEncryptor() {
|
||||
CryptoAssumptions.assumeCBCJCE();
|
||||
AesBytesEncryptor deprecated = new AesBytesEncryptor(this.password, this.hexSalt);
|
||||
byte[] encrypted = deprecated.encrypt(this.secret.getBytes());
|
||||
|
||||
AesCbcBytesEncryptor modern = AesCbcBytesEncryptor.withPassword(this.password, this.hexSalt).build();
|
||||
BytesEncryptor migrating = new MigratingBytesEncryptor("{CBC}", modern, deprecated);
|
||||
|
||||
assertThat(new String(migrating.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
|
||||
byte[] migrated = migrating.encrypt(this.secret.getBytes());
|
||||
assertThat(migrated[0]).isEqualTo((byte) '{');
|
||||
assertThat(migrated[1]).isEqualTo((byte) 'C');
|
||||
assertThat(migrated[2]).isEqualTo((byte) 'B');
|
||||
assertThat(migrated[3]).isEqualTo((byte) 'C');
|
||||
assertThat(migrated[4]).isEqualTo((byte) '}');
|
||||
assertThat(new String(migrating.decrypt(migrated))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withSecretWhenAesBytesEncryptorEncryptsThenDecrypts() {
|
||||
CryptoAssumptions.assumeCBCJCE();
|
||||
PBEKeySpec keySpec = new PBEKeySpec(this.password.toCharArray(), Hex.decode(this.hexSalt), 1024, 256);
|
||||
SecretKey key = CipherUtils.newSecretKey(SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name(), keySpec);
|
||||
AesBytesEncryptor deprecated = new AesBytesEncryptor(key, KeyGenerators.secureRandom(16),
|
||||
AesBytesEncryptor.CipherAlgorithm.CBC);
|
||||
AesCbcBytesEncryptor encryptor = AesCbcBytesEncryptor.withSecretKey(key).build();
|
||||
byte[] encrypted = deprecated.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void aesBytesEncryptorWhenEncryptsThenAesCbcBytesEncryptorDecrypts() {
|
||||
CryptoAssumptions.assumeCBCJCE();
|
||||
PBEKeySpec keySpec = new PBEKeySpec(this.password.toCharArray(), Hex.decode(this.hexSalt), 1024, 256);
|
||||
SecretKey key = CipherUtils.newSecretKey(SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name(), keySpec);
|
||||
AesCbcBytesEncryptor encryptor = AesCbcBytesEncryptor.withSecretKey(key).build();
|
||||
AesBytesEncryptor deprecated = new AesBytesEncryptor(key, KeyGenerators.secureRandom(16),
|
||||
AesBytesEncryptor.CipherAlgorithm.CBC);
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(deprecated.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
void roundtripWhenUsingCustomIvGeneratorThenEncryptsAndDecrypts() {
|
||||
CryptoAssumptions.assumeCBCJCE();
|
||||
BytesKeyGenerator customIvGenerator = mock(BytesKeyGenerator.class);
|
||||
given(customIvGenerator.generateKey()).willReturn(Hex.decode("4b0febebd439db7ca77153cb254520c3"));
|
||||
given(customIvGenerator.getKeyLength()).willReturn(16);
|
||||
AesCbcBytesEncryptor encryptor = AesCbcBytesEncryptor.withPassword(this.password, this.hexSalt)
|
||||
.ivGenerator(customIvGenerator)
|
||||
.build();
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withPasswordDerivesADifferentKeyThanAesBytesEncryptor() {
|
||||
CryptoAssumptions.assumeCBCJCE();
|
||||
AesBytesEncryptor deprecated = new AesBytesEncryptor(this.password, this.hexSalt,
|
||||
KeyGenerators.secureRandom(16));
|
||||
AesCbcBytesEncryptor encryptor = AesCbcBytesEncryptor.withPassword(this.password, this.hexSalt).build();
|
||||
byte[] encrypted = deprecated.encrypt(this.secret.getBytes());
|
||||
assertThatIllegalStateException().isThrownBy(() -> encryptor.decrypt(encrypted));
|
||||
}
|
||||
|
||||
private static final class MigratingBytesEncryptor implements BytesEncryptor {
|
||||
|
||||
private final byte[] prefix;
|
||||
|
||||
private final BytesEncryptor write;
|
||||
|
||||
private final BytesEncryptor deprecated;
|
||||
|
||||
MigratingBytesEncryptor(String prefix, BytesEncryptor write, BytesEncryptor deprecated) {
|
||||
this.prefix = prefix.getBytes(StandardCharsets.US_ASCII);
|
||||
this.write = write;
|
||||
this.deprecated = deprecated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encrypt(byte[] bytes) {
|
||||
return EncodingUtils.concatenate(this.prefix, this.write.encrypt(bytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] decrypt(byte[] encryptedBytes) {
|
||||
if (startsWith(encryptedBytes, this.prefix)) {
|
||||
byte[] bytes = EncodingUtils.subArray(encryptedBytes, this.prefix.length, encryptedBytes.length);
|
||||
return this.write.decrypt(bytes);
|
||||
}
|
||||
return this.deprecated.decrypt(encryptedBytes);
|
||||
}
|
||||
|
||||
private static boolean startsWith(byte[] data, byte[] prefix) {
|
||||
if (data.length < prefix.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < prefix.length; i++) {
|
||||
if (data[i] != prefix[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.security.crypto.encrypt;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AesGcmBytesEncryptor}.
|
||||
*/
|
||||
class AesGcmBytesEncryptorTests {
|
||||
|
||||
private final String secret = "value";
|
||||
|
||||
private final String password = "password";
|
||||
|
||||
private final String hexSalt = "deadbeef";
|
||||
|
||||
@Test
|
||||
void roundtripWhenUsingPasswordAndSaltThenEncryptsAndDecrypts() {
|
||||
CryptoAssumptions.assumeGCMJCE();
|
||||
AesGcmBytesEncryptor encryptor = AesGcmBytesEncryptor.withPassword(this.password, this.hexSalt).build();
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
void roundtripWhenUsingSecretKeyThenEncryptsAndDecrypts() {
|
||||
CryptoAssumptions.assumeGCMJCE();
|
||||
PBEKeySpec keySpec = new PBEKeySpec(this.password.toCharArray(), Hex.decode(this.hexSalt), 1024, 256);
|
||||
SecretKey secretKey = CipherUtils.newSecretKey(SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name(), keySpec);
|
||||
AesGcmBytesEncryptor encryptor = AesGcmBytesEncryptor.withSecretKey(secretKey).build();
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encryptWhenUsingMockIvThenProducesKnownCiphertext() {
|
||||
CryptoAssumptions.assumeGCMJCE();
|
||||
BytesKeyGenerator mockGenerator = mock(BytesKeyGenerator.class);
|
||||
given(mockGenerator.generateKey()).willReturn(Hex.decode("4b0febebd439db7ca77153cb254520c3"));
|
||||
given(mockGenerator.getKeyLength()).willReturn(16);
|
||||
AesGcmBytesEncryptor encryptor = AesGcmBytesEncryptor.withPassword(this.password, this.hexSalt)
|
||||
.ivGenerator(mockGenerator)
|
||||
.build();
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(Hex.encode(encrypted))
|
||||
.isEqualTo("4b0febebd439db7ca77153cb254520c3e4d61ae38207b4e42b820d311dc3d4e0e2f37ed5ee");
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encryptProducesUniqueOutputAndIvIsPrepended() {
|
||||
CryptoAssumptions.assumeGCMJCE();
|
||||
AesGcmBytesEncryptor encryptor = AesGcmBytesEncryptor.withPassword(this.password, this.hexSalt).build();
|
||||
byte[] first = encryptor.encrypt(this.secret.getBytes());
|
||||
byte[] second = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(first).isNotEqualTo(second);
|
||||
assertThat(first.length).isGreaterThan(32);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withSecretWhenAesBytesEncryptorEncryptsThenDecrypts() {
|
||||
CryptoAssumptions.assumeGCMJCE();
|
||||
PBEKeySpec keySpec = new PBEKeySpec(this.password.toCharArray(), Hex.decode(this.hexSalt), 1024, 256);
|
||||
SecretKey key = CipherUtils.newSecretKey(SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name(), keySpec);
|
||||
AesBytesEncryptor deprecated = new AesBytesEncryptor(key, KeyGenerators.secureRandom(16),
|
||||
AesBytesEncryptor.CipherAlgorithm.GCM);
|
||||
AesGcmBytesEncryptor encryptor = AesGcmBytesEncryptor.withSecretKey(key).build();
|
||||
byte[] encrypted = deprecated.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void aesBytesEncryptorWhenEncryptsThenAesGcmBytesEncryptorDecrypts() {
|
||||
CryptoAssumptions.assumeGCMJCE();
|
||||
PBEKeySpec keySpec = new PBEKeySpec(this.password.toCharArray(), Hex.decode(this.hexSalt), 1024, 256);
|
||||
SecretKey key = CipherUtils.newSecretKey(SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name(), keySpec);
|
||||
AesGcmBytesEncryptor encryptor = AesGcmBytesEncryptor.withSecretKey(key)
|
||||
.ivGenerator(KeyGenerators.secureRandom(12))
|
||||
.build();
|
||||
AesBytesEncryptor deprecated = new AesBytesEncryptor(key, KeyGenerators.secureRandom(12),
|
||||
AesBytesEncryptor.CipherAlgorithm.GCM);
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(deprecated.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
void roundtripWhenUsingCustomIvGeneratorLengthThenEncryptsAndDecrypts() {
|
||||
CryptoAssumptions.assumeGCMJCE();
|
||||
AesGcmBytesEncryptor encryptor = AesGcmBytesEncryptor.withPassword(this.password, this.hexSalt)
|
||||
.ivGenerator(KeyGenerators.secureRandom(12))
|
||||
.build();
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
assertThat(new String(encryptor.decrypt(encrypted))).isEqualTo(this.secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withPasswordDerivesADifferentKeyThanAesBytesEncryptor() {
|
||||
CryptoAssumptions.assumeGCMJCE();
|
||||
AesBytesEncryptor deprecated = new AesBytesEncryptor(this.password, this.hexSalt,
|
||||
KeyGenerators.secureRandom(16), AesBytesEncryptor.CipherAlgorithm.GCM);
|
||||
AesGcmBytesEncryptor encryptor = AesGcmBytesEncryptor.withPassword(this.password, this.hexSalt).build();
|
||||
byte[] encrypted = deprecated.encrypt(this.secret.getBytes());
|
||||
assertThatIllegalStateException().isThrownBy(() -> encryptor.decrypt(encrypted));
|
||||
}
|
||||
|
||||
@Test
|
||||
void decryptDetectsAuthenticationTagTampering() {
|
||||
CryptoAssumptions.assumeGCMJCE();
|
||||
AesGcmBytesEncryptor encryptor = AesGcmBytesEncryptor.withPassword(this.password, this.hexSalt).build();
|
||||
byte[] encrypted = encryptor.encrypt(this.secret.getBytes());
|
||||
encrypted[17] ^= 0xFF;
|
||||
assertThatIllegalStateException().isThrownBy(() -> encryptor.decrypt(encrypted));
|
||||
}
|
||||
|
||||
}
|
||||
+4
-6
@@ -24,25 +24,23 @@ import javax.crypto.NoSuchPaddingException;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.opentest4j.TestAbortedException;
|
||||
|
||||
import org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm;
|
||||
|
||||
public final class CryptoAssumptions {
|
||||
|
||||
private CryptoAssumptions() {
|
||||
}
|
||||
|
||||
public static void assumeGCMJCE() {
|
||||
assumeAes256(CipherAlgorithm.GCM);
|
||||
assumeAes256("GCM");
|
||||
}
|
||||
|
||||
public static void assumeCBCJCE() {
|
||||
assumeAes256(CipherAlgorithm.CBC);
|
||||
assumeAes256("CBC");
|
||||
}
|
||||
|
||||
private static void assumeAes256(CipherAlgorithm cipherAlgorithm) {
|
||||
private static void assumeAes256(String cipherAlgorithm) {
|
||||
boolean aes256Available = false;
|
||||
try {
|
||||
Cipher.getInstance(cipherAlgorithm.toString());
|
||||
Cipher.getInstance(cipherAlgorithm);
|
||||
aes256Available = Cipher.getMaxAllowedKeyLength("AES") >= 256;
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex) {
|
||||
|
||||
@@ -8,8 +8,8 @@ The code is distributed as part of the core module but has no dependencies on an
|
||||
|
||||
[[spring-security-crypto-encryption]]
|
||||
== Encryptors
|
||||
The javadoc:org.springframework.security.crypto.encrypt.Encryptors[] class provides factory methods for constructing symmetric encryptors.
|
||||
This class lets you create javadoc:org.springframework.security.crypto.encrypt.BytesEncryptor[] instances to encrypt data in raw `byte[]` form.
|
||||
Spring Security provides javadoc:org.springframework.security.crypto.encrypt.AesGcmBytesEncryptor[] and javadoc:org.springframework.security.crypto.encrypt.AesCbcBytesEncryptor[] for constructing symmetric encryptors.
|
||||
These can be used to encrypt data in raw `byte[]` form.
|
||||
You can also construct javadoc:org.springframework.security.crypto.encrypt.TextEncryptor[] instances to encrypt text strings.
|
||||
Encryptors are thread-safe.
|
||||
|
||||
@@ -20,7 +20,7 @@ Both `BytesEncryptor` and `TextEncryptor` are interfaces. `BytesEncryptor` has m
|
||||
|
||||
[[spring-security-crypto-encryption-bytes]]
|
||||
=== BytesEncryptor
|
||||
You can use the `Encryptors.stronger` factory method to construct a `BytesEncryptor`:
|
||||
Use `AesGcmBytesEncryptor` to construct a BytesEncryptor with authenticated encryption:
|
||||
|
||||
.BytesEncryptor
|
||||
[tabs]
|
||||
@@ -29,24 +29,22 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Encryptors.stronger("password", "salt");
|
||||
AesGcmBytesEncryptor.withPassword("password", "salt").build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
Encryptors.stronger("password", "salt")
|
||||
AesGcmBytesEncryptor.withPassword("password", "salt").build()
|
||||
----
|
||||
======
|
||||
|
||||
The `stronger` encryption method creates an encryptor by using 256-bit AES encryption with
|
||||
Galois Counter Mode (GCM).
|
||||
It derives the secret key by using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
|
||||
This method requires Java 6.
|
||||
The password used to generate the `SecretKey` should be kept in a secure place and should not be shared.
|
||||
The salt is used to prevent dictionary attacks against the key in the event that your encrypted data is compromised.
|
||||
A 16-byte random initialization vector is also applied so that each encrypted message is unique.
|
||||
`AesGcmBytesEncryptor` uses 256-bit AES encryption with Galois Counter Mode (GCM), providing https://en.wikipedia.org/wiki/Authenticated_encryption[authenticated encryption] (AEAD).
|
||||
It derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
|
||||
The password used to generate the SecretKey should be kept in a secure place and not be shared.
|
||||
The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised.
|
||||
A 16-byte random initialization vector is also applied so each encrypted message is unique.
|
||||
|
||||
The provided salt should be in hex-encoded String form, be random, and be at least 8 bytes in length.
|
||||
You can generate such a salt by using a `KeyGenerator`:
|
||||
@@ -69,14 +67,33 @@ val salt = KeyGenerators.string().generateKey() // generates a random 8-byte sal
|
||||
----
|
||||
======
|
||||
|
||||
You can also use the `standard` encryption method, which is 256-bit AES in Cipher Block Chaining (CBC) Mode.
|
||||
Users who require CBC mode may use `AesCbcBytesEncryptor`:
|
||||
|
||||
.AesCbcBytesEncryptor
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
AesCbcBytesEncryptor.withPassword("password", "salt").build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
AesCbcBytesEncryptor.withPassword("password", "salt").build()
|
||||
----
|
||||
======
|
||||
|
||||
This mode is not https://en.wikipedia.org/wiki/Authenticated_encryption[authenticated] and does not provide any
|
||||
guarantees about the authenticity of the data.
|
||||
For a more secure alternative, use `Encryptors.stronger`.
|
||||
For a more secure alternative, users should prefer `AesGcmBytesEncryptor`.
|
||||
|
||||
[[spring-security-crypto-encryption-text]]
|
||||
=== TextEncryptor
|
||||
You can use the `Encryptors.text` factory method to construct a standard TextEncryptor:
|
||||
Use `AesCbcBytesEncryptor` to encrypt text data:
|
||||
|
||||
.TextEncryptor
|
||||
[tabs]
|
||||
@@ -85,19 +102,22 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Encryptors.text("password", "salt");
|
||||
AesCbcBytesEncryptor.withPassword("password", "salt").build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
Encryptors.text("password", "salt")
|
||||
AesCbcBytesEncryptor.withPassword("password", "salt").build()
|
||||
----
|
||||
======
|
||||
|
||||
A `TextEncryptor` uses a standard `BytesEncryptor` to encrypt text data.
|
||||
Encrypted results are returned as hex-encoded strings for easy storage on the filesystem or in a database.
|
||||
`AesCbcBytesEncryptor` encrypts data as raw bytes.
|
||||
You can hex-encode the result for easy storage on the filesystem or in the database.
|
||||
|
||||
NOTE: Queryable text encryption (encrypting such that the same plaintext always produces the same ciphertext) is no longer recommended, as it relies on a fixed initialization vector and does not provide adequate security.
|
||||
Instead, look to your data store for a mechanism to query encrypted data.
|
||||
|
||||
[[spring-security-crypto-keygenerators]]
|
||||
== Key Generators
|
||||
|
||||
Reference in New Issue
Block a user