SEC-1659: favor AES encryption instead of DES as standard symmetric encryption algorithm
This commit is contained in:
parent
ffa7301e7f
commit
ea76efdb2c
|
@ -24,61 +24,62 @@ import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
import org.springframework.security.crypto.util.EncodingUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A general purpose encryptor for password-based encryption (PBEwith{prf}and{encryption} algorithms).
|
* Encryptor that uses 256-bit AES encryption.
|
||||||
* Prepends a random salt to each encrypted value to aid in the prevention of password compromise with the aid of a dictionary/rainbow table.
|
|
||||||
* The salt allows the same secret key to be used for multiple encryption operations.
|
|
||||||
* The password should be not be shared.
|
|
||||||
* Note: {prf} = Pseudo random function e.g. MD5; {encryption} = Encryption method e.g. DES or AES.
|
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
final class PasswordBasedBytesEncryptor implements BytesEncryptor {
|
final class AesBytesEncryptor implements BytesEncryptor {
|
||||||
|
|
||||||
private final SecretKey secretKey;
|
private final SecretKey secretKey;
|
||||||
|
|
||||||
private final BytesKeyGenerator saltGenerator;
|
|
||||||
|
|
||||||
private final Cipher encryptor;
|
private final Cipher encryptor;
|
||||||
|
|
||||||
private final Cipher decryptor;
|
private final Cipher decryptor;
|
||||||
|
|
||||||
public PasswordBasedBytesEncryptor(String algorithm, String password) {
|
private final BytesKeyGenerator ivGenerator;
|
||||||
secretKey = newSecretKey(algorithm, password);
|
|
||||||
saltGenerator = KeyGenerators.secureRandom();
|
public AesBytesEncryptor(String password, String salt, BytesKeyGenerator ivGenerator) {
|
||||||
encryptor = newCipher(algorithm);
|
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), EncodingUtils.hexDecode(salt), 1024, 256);
|
||||||
decryptor = newCipher(algorithm);
|
SecretKey secretKey = newSecretKey("PBKDF2WithHmacSHA1", keySpec);
|
||||||
|
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
|
||||||
|
encryptor = newCipher(AES_ALGORITHM);
|
||||||
|
decryptor = newCipher(AES_ALGORITHM);
|
||||||
|
this.ivGenerator = ivGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] encrypt(byte[] bytes) {
|
public byte[] encrypt(byte[] bytes) {
|
||||||
byte[] salt = saltGenerator.generateKey();
|
|
||||||
byte[] encrypted;
|
|
||||||
synchronized (encryptor) {
|
synchronized (encryptor) {
|
||||||
initCipher(encryptor, Cipher.ENCRYPT_MODE, secretKey, salt, 1024);
|
byte[] iv = ivGenerator.generateKey();
|
||||||
encrypted = doFinal(encryptor, bytes);
|
initCipher(encryptor, Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
|
||||||
|
byte[] encrypted = doFinal(encryptor, bytes);
|
||||||
|
return concatenate(iv, encrypted);
|
||||||
}
|
}
|
||||||
return concatenate(salt, encrypted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] decrypt(byte[] encryptedBytes) {
|
public byte[] decrypt(byte[] encryptedBytes) {
|
||||||
byte[] salt = saltPart(encryptedBytes);
|
|
||||||
byte[] decrypted;
|
|
||||||
synchronized (decryptor) {
|
synchronized (decryptor) {
|
||||||
initCipher(decryptor, Cipher.DECRYPT_MODE, secretKey, salt, 1024);
|
byte[] iv = ivPart(encryptedBytes);
|
||||||
decrypted = doFinal(decryptor, cipherPart(encryptedBytes, salt));
|
initCipher(decryptor, Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
|
||||||
|
return doFinal(decryptor, cipherPart(encryptedBytes, iv));
|
||||||
}
|
}
|
||||||
return decrypted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] saltPart(byte[] encrypted) {
|
// internal helpers
|
||||||
return subArray(encrypted, 0, saltGenerator.getKeyLength());
|
|
||||||
|
private byte[] ivPart(byte[] encrypted) {
|
||||||
|
return subArray(encrypted, 0, ivGenerator.getKeyLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] cipherPart(byte[] encrypted, byte[] salt) {
|
private byte[] cipherPart(byte[] encrypted, byte[] iv) {
|
||||||
return subArray(encrypted, salt.length, encrypted.length);
|
return subArray(encrypted, iv.length, encrypted.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
|
||||||
}
|
}
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.crypto.encrypt;
|
package org.springframework.security.crypto.encrypt;
|
||||||
|
|
||||||
|
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for commonly used encryptors.
|
* Factory for commonly used encryptors.
|
||||||
* Defines the public API for constructing {@link BytesEncryptor} and {@link TextEncryptor} implementations.
|
* Defines the public API for constructing {@link BytesEncryptor} and {@link TextEncryptor} implementations.
|
||||||
|
@ -23,14 +25,17 @@ package org.springframework.security.crypto.encrypt;
|
||||||
public class Encryptors {
|
public class Encryptors {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a standard password-based bytes encryptor.
|
* Creates a standard password-based bytes encryptor using 256 bit AES encryption.
|
||||||
* Uses MD5 PRF hashing with 1024 iterations and DES-based encryption.
|
* Derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
|
||||||
* Salts each encrypted value to ensure it will be unique.
|
* Salts the password to prevent dictionary attacks against the key.
|
||||||
* TODO - switch standard algorithm from DES to AES. Switch hashing to SHA-1 from MD5.
|
* 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.
|
||||||
* @param password the password used to generate the encryptor's secret key; should not be shared
|
* @param password the password used to generate the encryptor's secret key; should not be shared
|
||||||
|
* @param salt an hex-encoded, random, site-global salt value to use to generate the key
|
||||||
*/
|
*/
|
||||||
public static BytesEncryptor standard(String password) {
|
public static BytesEncryptor standard(String password, String salt) {
|
||||||
return new PasswordBasedBytesEncryptor(PBE_MD5_DES_ALGORITHM, password);
|
return new AesBytesEncryptor(password, password, KeyGenerators.secureRandom(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,37 +43,33 @@ public class Encryptors {
|
||||||
* Encrypted text is hex-encoded.
|
* Encrypted text is hex-encoded.
|
||||||
* @param password the password used to generate the encryptor's secret key; should not be shared
|
* @param password the password used to generate the encryptor's secret key; should not be shared
|
||||||
*/
|
*/
|
||||||
public static TextEncryptor text(String password) {
|
public static TextEncryptor text(String password, String salt) {
|
||||||
return new HexEncodingTextEncryptor(standard(password));
|
return new HexEncodingTextEncryptor(standard(password, salt));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an encryptor for queryable text strings that uses standard password-based encryption.
|
* Creates an encryptor for queryable text strings that uses standard password-based encryption.
|
||||||
* The hex-encoded salt string provided should be random and is used to protect against password dictionary attacks.
|
* Uses a shared, or constant 16 byte initialization vector so encrypting the same data results in the same encryption result.
|
||||||
* Does not salt each encrypted value so an encrypted value may be queried against.
|
* This is done to allow encrypted data to be queried against.
|
||||||
* Encrypted text is hex-encoded.
|
* Encrypted text is hex-encoded.
|
||||||
* @param password the password used to generate the encryptor's secret key; should not be shared
|
* @param password the password used to generate the encryptor's secret key; should not be shared
|
||||||
* @param salt an hex-encoded, random, site-global salt value to use to initialize the cipher
|
* @param salt an hex-encoded, random, site-global salt value to use to generate the secret key
|
||||||
*/
|
*/
|
||||||
public static TextEncryptor queryableText(String password, String salt) {
|
public static TextEncryptor queryableText(String password, String salt) {
|
||||||
return new QueryableTextEncryptor(PBE_MD5_DES_ALGORITHM, password, salt);
|
return new HexEncodingTextEncryptor(new AesBytesEncryptor(password, salt, KeyGenerators.shared(16)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a text encrypter that performs no encryption.
|
* Creates a text encryptor that performs no encryption.
|
||||||
* Useful for test environments where working with plain text strings is desired for simplicity.
|
* Useful for developer testing environments where working with plain text strings is desired for simplicity.
|
||||||
*/
|
*/
|
||||||
public static TextEncryptor noOpText() {
|
public static TextEncryptor noOpText() {
|
||||||
return NO_OP_TEXT_INSTANCE;
|
return NO_OP_TEXT_INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal helpers
|
|
||||||
|
|
||||||
private Encryptors() {
|
private Encryptors() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String PBE_MD5_DES_ALGORITHM = "PBEWithMD5AndDES";
|
|
||||||
|
|
||||||
private static final TextEncryptor NO_OP_TEXT_INSTANCE = new NoOpTextEncryptor();
|
private static final TextEncryptor NO_OP_TEXT_INSTANCE = new NoOpTextEncryptor();
|
||||||
|
|
||||||
private static final class NoOpTextEncryptor implements TextEncryptor {
|
private static final class NoOpTextEncryptor implements TextEncryptor {
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2011 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
|
|
||||||
*
|
|
||||||
* 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.springframework.security.crypto.encrypt;
|
|
||||||
|
|
||||||
import static org.springframework.security.crypto.util.CipherUtils.doFinal;
|
|
||||||
import static org.springframework.security.crypto.util.CipherUtils.initCipher;
|
|
||||||
import static org.springframework.security.crypto.util.CipherUtils.newCipher;
|
|
||||||
import static org.springframework.security.crypto.util.CipherUtils.newSecretKey;
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.hexDecode;
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.hexEncode;
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.utf8Decode;
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.utf8Encode;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A text encryptor that applies password-based MD5 plus DES symmetric key encryption.
|
|
||||||
* Designed to be used to encrypt fields that are queryable; for example, an indexed field such as an OAuth apiKey.
|
|
||||||
* Requires a random site-global salt to protect against password dictionary attacks.
|
|
||||||
* Does not salt on each {@link #encrypt(String)} operation to allow the encrypted field to be queried.
|
|
||||||
* @author Keith Donald
|
|
||||||
*/
|
|
||||||
final class QueryableTextEncryptor implements TextEncryptor {
|
|
||||||
|
|
||||||
private final Cipher encryptor;
|
|
||||||
|
|
||||||
private final Cipher decryptor;
|
|
||||||
|
|
||||||
public QueryableTextEncryptor(String algorithm, String password, String salt) {
|
|
||||||
byte[] saltBytes = hexDecode(salt);
|
|
||||||
SecretKey secretKey = newSecretKey(algorithm, password);
|
|
||||||
encryptor = newCipher(algorithm);
|
|
||||||
initCipher(encryptor, Cipher.ENCRYPT_MODE, secretKey, saltBytes, 1000);
|
|
||||||
decryptor = newCipher(algorithm);
|
|
||||||
initCipher(decryptor, Cipher.DECRYPT_MODE, secretKey, saltBytes, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String encrypt(String text) {
|
|
||||||
return hexEncode(doFinal(encryptor, utf8Encode(text)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String decrypt(String encryptedText) {
|
|
||||||
return utf8Decode(doFinal(decryptor, hexDecode(encryptedText)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -39,6 +39,14 @@ public class KeyGenerators {
|
||||||
return new SecureRandomBytesKeyGenerator(keyLength);
|
return new SecureRandomBytesKeyGenerator(keyLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link BytesKeyGenerator} that returns a single, shared {@link SecureRandom} key of a custom length.
|
||||||
|
* @param keyLength the key length in bytes, e.g. 16, for a 16 byte key.
|
||||||
|
*/
|
||||||
|
public static BytesKeyGenerator shared(int keyLength) {
|
||||||
|
return new SharedKeyGenerator(secureRandom(16).generateKey());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link StringKeyGenerator} that hex-encodes {@link SecureRandom} keys of 8 bytes in length.
|
* Creates a {@link StringKeyGenerator} that hex-encodes {@link SecureRandom} keys of 8 bytes in length.
|
||||||
* The hex-encoded string is keyLength * 2 characters in length.
|
* The hex-encoded string is keyLength * 2 characters in length.
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2011 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
|
||||||
|
*
|
||||||
|
* 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.springframework.security.crypto.keygen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key generator that simply returns the same key every time.
|
||||||
|
*
|
||||||
|
* @author Keith Donald
|
||||||
|
* @author Annabelle Donald
|
||||||
|
*/
|
||||||
|
final class SharedKeyGenerator implements BytesKeyGenerator {
|
||||||
|
|
||||||
|
private byte[] sharedKey;
|
||||||
|
|
||||||
|
public SharedKeyGenerator(byte[] sharedKey) {
|
||||||
|
this.sharedKey = sharedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKeyLength() {
|
||||||
|
return sharedKey.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] generateKey() {
|
||||||
|
return sharedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,7 +18,9 @@ package org.springframework.security.crypto.util;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.InvalidParameterSpecException;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
@ -38,15 +40,21 @@ public class CipherUtils {
|
||||||
/**
|
/**
|
||||||
* Generates a SecretKey.
|
* Generates a SecretKey.
|
||||||
*/
|
*/
|
||||||
public static SecretKey newSecretKey(String algorithm, String secret) {
|
public static SecretKey newSecretKey(String algorithm, String password) {
|
||||||
|
return newSecretKey(algorithm, new PBEKeySpec(password.toCharArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a SecretKey.
|
||||||
|
*/
|
||||||
|
public static SecretKey newSecretKey(String algorithm, PBEKeySpec keySpec) {
|
||||||
try {
|
try {
|
||||||
PBEKeySpec pbeKeySpec = new PBEKeySpec(secret.toCharArray());
|
|
||||||
SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
|
SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
|
||||||
return factory.generateSecret(pbeKeySpec);
|
return factory.generateSecret(keySpec);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new IllegalArgumentException("Not a valid encryption algorithm", e);
|
throw new IllegalArgumentException("Not a valid encryption algorithm", e);
|
||||||
} catch (InvalidKeySpecException e) {
|
} catch (InvalidKeySpecException e) {
|
||||||
throw new IllegalArgumentException("Not a valid secert key", e);
|
throw new IllegalArgumentException("Not a valid secret key", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,9 +74,38 @@ public class CipherUtils {
|
||||||
/**
|
/**
|
||||||
* Initializes the Cipher for use.
|
* Initializes the Cipher for use.
|
||||||
*/
|
*/
|
||||||
public static void initCipher(Cipher cipher, int mode, SecretKey secretKey, byte[] salt, int iterationCount) {
|
public static <T extends AlgorithmParameterSpec> T getParameterSpec(Cipher cipher, Class<T> parameterSpecClass) {
|
||||||
try {
|
try {
|
||||||
cipher.init(mode, secretKey, new PBEParameterSpec(salt, iterationCount));
|
return cipher.getParameters().getParameterSpec(parameterSpecClass);
|
||||||
|
} catch (InvalidParameterSpecException e) {
|
||||||
|
throw new IllegalArgumentException("Unable to access parameter", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the Cipher for use.
|
||||||
|
*/
|
||||||
|
public static void initCipher(Cipher cipher, int mode, SecretKey secretKey) {
|
||||||
|
initCipher(cipher, mode, secretKey, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the Cipher for use.
|
||||||
|
*/
|
||||||
|
public static void initCipher(Cipher cipher, int mode, SecretKey secretKey, byte[] salt, int iterationCount) {
|
||||||
|
initCipher(cipher, mode, secretKey, new PBEParameterSpec(salt, iterationCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the Cipher for use.
|
||||||
|
*/
|
||||||
|
public static void initCipher(Cipher cipher, int mode, SecretKey secretKey, AlgorithmParameterSpec parameterSpec) {
|
||||||
|
try {
|
||||||
|
if (parameterSpec != null) {
|
||||||
|
cipher.init(mode, secretKey, parameterSpec);
|
||||||
|
} else {
|
||||||
|
cipher.init(mode, secretKey);
|
||||||
|
}
|
||||||
} catch (InvalidKeyException e) {
|
} catch (InvalidKeyException e) {
|
||||||
throw new IllegalArgumentException("Unable to initialize due to invalid secret key", e);
|
throw new IllegalArgumentException("Unable to initialize due to invalid secret key", e);
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ public class EncryptorsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void standard() {
|
public void standard() {
|
||||||
BytesEncryptor encryptor = Encryptors.standard("password");
|
BytesEncryptor encryptor = Encryptors.standard("password", "5c0744940b5c369b");
|
||||||
byte[] result = encryptor.encrypt("text".getBytes());
|
byte[] result = encryptor.encrypt("text".getBytes());
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
assertFalse(new String(result).equals("text"));
|
assertFalse(new String(result).equals("text"));
|
||||||
|
@ -21,7 +21,7 @@ public class EncryptorsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void text() {
|
public void text() {
|
||||||
TextEncryptor encryptor = Encryptors.text("password");
|
TextEncryptor encryptor = Encryptors.text("password", "5c0744940b5c369b");
|
||||||
String result = encryptor.encrypt("text");
|
String result = encryptor.encrypt("text");
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
assertFalse(result.equals("text"));
|
assertFalse(result.equals("text"));
|
||||||
|
@ -45,5 +45,4 @@ public class EncryptorsTests {
|
||||||
assertEquals("text", encryptor.encrypt("text"));
|
assertEquals("text", encryptor.encrypt("text"));
|
||||||
assertEquals("text", encryptor.decrypt("text"));
|
assertEquals("text", encryptor.decrypt("text"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue