Migrate spring-security-rsa into spring-security-crypto

Closes gh-14202
This commit is contained in:
Marcus Hert Da Coregio 2023-12-27 10:45:59 -03:00
parent 45f8ab3401
commit 6f7b9bbfde
17 changed files with 1320 additions and 1 deletions

View File

@ -3,8 +3,9 @@ apply plugin: 'io.spring.convention.spring-module'
dependencies {
management platform(project(":spring-security-dependencies"))
optional 'org.springframework:spring-jcl'
optional 'org.springframework:spring-core'
optional 'org.bouncycastle:bcpkix-jdk15on'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"

View File

@ -0,0 +1,96 @@
/*
* Copyright 2013-2024 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.io.InputStream;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.RSAPublicKeySpec;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
/**
* @author Dave Syer
* @author Tim Ysewyn
* @since 6.3
*/
public class KeyStoreKeyFactory {
private final Resource resource;
private final char[] password;
private KeyStore store;
private final Object lock = new Object();
private final String type;
public KeyStoreKeyFactory(Resource resource, char[] password) {
this(resource, password, type(resource));
}
private static String type(Resource resource) {
String ext = StringUtils.getFilenameExtension(resource.getFilename());
return (ext != null) ? ext : "jks";
}
public KeyStoreKeyFactory(Resource resource, char[] password, String type) {
this.resource = resource;
this.password = password;
this.type = type;
}
public KeyPair getKeyPair(String alias) {
return getKeyPair(alias, this.password);
}
public KeyPair getKeyPair(String alias, char[] password) {
try {
synchronized (this.lock) {
if (this.store == null) {
synchronized (this.lock) {
this.store = KeyStore.getInstance(this.type);
try (InputStream stream = this.resource.getInputStream()) {
this.store.load(stream, this.password);
}
}
}
}
RSAPrivateCrtKey key = (RSAPrivateCrtKey) this.store.getKey(alias, password);
Certificate certificate = this.store.getCertificate(alias);
PublicKey publicKey = null;
if (certificate != null) {
publicKey = certificate.getPublicKey();
}
else if (key != null) {
RSAPublicKeySpec spec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent());
publicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
}
return new KeyPair(publicKey, key);
}
catch (Exception ex) {
throw new IllegalStateException("Cannot load keys from store: " + this.resource, ex);
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2013-2024 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;
/**
* @author Dave Syer
* @since 6.3
*/
public enum RsaAlgorithm {
DEFAULT("RSA", 117), OAEP("RSA/ECB/OAEPPadding", 86);
private final String name;
private final int maxLength;
RsaAlgorithm(String name, int maxLength) {
this.name = name;
this.maxLength = maxLength;
}
public String getJceName() {
return this.name;
}
public int getMaxLength() {
return this.maxLength;
}
}

View File

@ -0,0 +1,284 @@
/*
* Copyright 2013-2024 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bouncycastle.asn1.ASN1Sequence;
/**
* Reads RSA key pairs using BC provider classes but without the need to specify a crypto
* provider or have BC added as one.
*
* @author Luke Taylor
* @author Dave Syer
*/
final class RsaKeyHelper {
private static final Charset UTF8 = StandardCharsets.UTF_8;
private static final String BEGIN = "-----BEGIN";
private static final Pattern PEM_DATA = Pattern.compile(".*-----BEGIN (.*)-----(.*)-----END (.*)-----",
Pattern.DOTALL);
private static final byte[] PREFIX = new byte[] { 0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a' };
private RsaKeyHelper() {
}
static KeyPair parseKeyPair(String pemData) {
Matcher m = PEM_DATA.matcher(pemData.replaceAll("\n *", "").trim());
if (!m.matches()) {
try {
RSAPublicKey publicValue = extractPublicKey(pemData);
if (publicValue != null) {
return new KeyPair(publicValue, null);
}
}
catch (Exception ex) {
// Ignore
}
throw new IllegalArgumentException("String is not PEM encoded data, nor a public key encoded for ssh");
}
String type = m.group(1);
final byte[] content = base64Decode(m.group(2));
PublicKey publicKey;
PrivateKey privateKey = null;
try {
KeyFactory fact = KeyFactory.getInstance("RSA");
switch (type) {
case "RSA PRIVATE KEY" -> {
ASN1Sequence seq = ASN1Sequence.getInstance(content);
if (seq.size() != 9) {
throw new IllegalArgumentException("Invalid RSA Private Key ASN1 sequence.");
}
org.bouncycastle.asn1.pkcs.RSAPrivateKey key = org.bouncycastle.asn1.pkcs.RSAPrivateKey
.getInstance(seq);
RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent());
RSAPrivateCrtKeySpec privSpec = new RSAPrivateCrtKeySpec(key.getModulus(), key.getPublicExponent(),
key.getPrivateExponent(), key.getPrime1(), key.getPrime2(), key.getExponent1(),
key.getExponent2(), key.getCoefficient());
publicKey = fact.generatePublic(pubSpec);
privateKey = fact.generatePrivate(privSpec);
}
case "PUBLIC KEY" -> {
KeySpec keySpec = new X509EncodedKeySpec(content);
publicKey = fact.generatePublic(keySpec);
}
case "RSA PUBLIC KEY" -> {
ASN1Sequence seq = ASN1Sequence.getInstance(content);
org.bouncycastle.asn1.pkcs.RSAPublicKey key = org.bouncycastle.asn1.pkcs.RSAPublicKey
.getInstance(seq);
RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent());
publicKey = fact.generatePublic(pubSpec);
}
default -> throw new IllegalArgumentException(type + " is not a supported format");
}
return new KeyPair(publicKey, privateKey);
}
catch (InvalidKeySpecException ex) {
throw new RuntimeException(ex);
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException(ex);
}
}
private static byte[] base64Decode(String string) {
try {
ByteBuffer bytes = UTF8.newEncoder().encode(CharBuffer.wrap(string));
byte[] bytesCopy = new byte[bytes.limit()];
System.arraycopy(bytes.array(), 0, bytesCopy, 0, bytes.limit());
return Base64.getDecoder().decode(bytesCopy);
}
catch (CharacterCodingException ex) {
throw new RuntimeException(ex);
}
}
static String base64Encode(byte[] bytes) {
try {
return UTF8.newDecoder().decode(ByteBuffer.wrap(Base64.getEncoder().encode(bytes))).toString();
}
catch (CharacterCodingException ex) {
throw new RuntimeException(ex);
}
}
static KeyPair generateKeyPair() {
try {
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
return keyGen.generateKeyPair();
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException(ex);
}
}
private static final Pattern SSH_PUB_KEY = Pattern.compile("ssh-(rsa|dsa) ([A-Za-z0-9/+]+=*) (.*)");
private static RSAPublicKey extractPublicKey(String key) {
Matcher m = SSH_PUB_KEY.matcher(key);
if (m.matches()) {
String alg = m.group(1);
String encKey = m.group(2);
// String id = m.group(3);
if (!"rsa".equalsIgnoreCase(alg)) {
throw new IllegalArgumentException("Only RSA is currently supported, but algorithm was " + alg);
}
return parseSSHPublicKey(encKey);
}
else if (!key.startsWith(BEGIN)) {
// Assume it's the plain Base64 encoded ssh key without the
// "ssh-rsa" at the start
return parseSSHPublicKey(key);
}
return null;
}
static RSAPublicKey parsePublicKey(String key) {
RSAPublicKey publicKey = extractPublicKey(key);
if (publicKey != null) {
return publicKey;
}
KeyPair kp = parseKeyPair(key);
if (kp.getPublic() == null) {
throw new IllegalArgumentException("Key data does not contain a public key");
}
return (RSAPublicKey) kp.getPublic();
}
static String encodePublicKey(RSAPublicKey key, String id) {
StringWriter output = new StringWriter();
output.append("ssh-rsa ");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
stream.write(PREFIX);
writeBigInteger(stream, key.getPublicExponent());
writeBigInteger(stream, key.getModulus());
}
catch (IOException ex) {
throw new IllegalStateException("Cannot encode key", ex);
}
output.append(base64Encode(stream.toByteArray()));
output.append(" " + id);
return output.toString();
}
private static RSAPublicKey parseSSHPublicKey(String encKey) {
ByteArrayInputStream in = new ByteArrayInputStream(base64Decode(encKey));
byte[] prefix = new byte[11];
try {
if (in.read(prefix) != 11 || !Arrays.equals(PREFIX, prefix)) {
throw new IllegalArgumentException("SSH key prefix not found");
}
BigInteger e = new BigInteger(readBigInteger(in));
BigInteger n = new BigInteger(readBigInteger(in));
return createPublicKey(n, e);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
static RSAPublicKey createPublicKey(BigInteger n, BigInteger e) {
try {
return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(n, e));
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static void writeBigInteger(ByteArrayOutputStream stream, BigInteger num) throws IOException {
int length = num.toByteArray().length;
byte[] data = new byte[4];
data[0] = (byte) ((length >> 24) & 0xFF);
data[1] = (byte) ((length >> 16) & 0xFF);
data[2] = (byte) ((length >> 8) & 0xFF);
data[3] = (byte) (length & 0xFF);
stream.write(data);
stream.write(num.toByteArray());
}
private static byte[] readBigInteger(ByteArrayInputStream in) throws IOException {
byte[] b = new byte[4];
if (in.read(b) != 4) {
throw new IOException("Expected length data as 4 bytes");
}
int l = ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) | ((b[2] & 0xFF) << 8) | (b[3] & 0xFF);
b = new byte[l];
if (in.read(b) != l) {
throw new IOException("Expected " + l + " key bytes");
}
return b;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2013-2024 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;
/**
* @author Dave Syer
* @since 6.3
*/
public interface RsaKeyHolder {
String getPublicKey();
}

View File

@ -0,0 +1,168 @@
/*
* Copyright 2013-2024 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.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
import javax.crypto.Cipher;
/**
* @author Dave Syer
* @since 6.3
*/
public class RsaRawEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHolder {
private static final String DEFAULT_ENCODING = "UTF-8";
private RsaAlgorithm algorithm = RsaAlgorithm.DEFAULT;
private Charset charset;
private RSAPublicKey publicKey;
private RSAPrivateKey privateKey;
private Charset defaultCharset;
public RsaRawEncryptor(RsaAlgorithm algorithm) {
this(RsaKeyHelper.generateKeyPair(), algorithm);
}
public RsaRawEncryptor() {
this(RsaKeyHelper.generateKeyPair());
}
public RsaRawEncryptor(KeyPair keyPair, RsaAlgorithm algorithm) {
this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm);
}
public RsaRawEncryptor(KeyPair keyPair) {
this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate());
}
public RsaRawEncryptor(String pemData) {
this(RsaKeyHelper.parseKeyPair(pemData));
}
public RsaRawEncryptor(PublicKey publicKey) {
this(DEFAULT_ENCODING, publicKey, null);
}
public RsaRawEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey) {
this(encoding, publicKey, privateKey, RsaAlgorithm.DEFAULT);
}
public RsaRawEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm) {
this.charset = Charset.forName(encoding);
this.publicKey = (RSAPublicKey) publicKey;
this.privateKey = (RSAPrivateKey) privateKey;
this.defaultCharset = Charset.forName(DEFAULT_ENCODING);
this.algorithm = algorithm;
}
@Override
public String getPublicKey() {
return RsaKeyHelper.encodePublicKey(this.publicKey, "application");
}
@Override
public String encrypt(String text) {
return new String(Base64.getEncoder().encode(encrypt(text.getBytes(this.charset))), this.defaultCharset);
}
@Override
public String decrypt(String encryptedText) {
if (this.privateKey == null) {
throw new IllegalStateException("Private key must be provided for decryption");
}
return new String(decrypt(Base64.getDecoder().decode(encryptedText.getBytes(this.defaultCharset))),
this.charset);
}
@Override
public byte[] encrypt(byte[] byteArray) {
return encrypt(byteArray, this.publicKey, this.algorithm);
}
@Override
public byte[] decrypt(byte[] encryptedByteArray) {
return decrypt(encryptedByteArray, this.privateKey, this.algorithm);
}
private static byte[] encrypt(byte[] text, PublicKey key, RsaAlgorithm alg) {
ByteArrayOutputStream output = new ByteArrayOutputStream(text.length);
try {
final Cipher cipher = Cipher.getInstance(alg.getJceName());
int limit = Math.min(text.length, alg.getMaxLength());
int pos = 0;
while (pos < text.length) {
cipher.init(Cipher.ENCRYPT_MODE, key);
cipher.update(text, pos, limit);
pos += limit;
limit = Math.min(text.length - pos, alg.getMaxLength());
byte[] buffer = cipher.doFinal();
output.write(buffer, 0, buffer.length);
}
return output.toByteArray();
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new IllegalStateException("Cannot encrypt", ex);
}
}
private static byte[] decrypt(byte[] text, RSAPrivateKey key, RsaAlgorithm alg) {
ByteArrayOutputStream output = new ByteArrayOutputStream(text.length);
try {
final Cipher cipher = Cipher.getInstance(alg.getJceName());
int maxLength = getByteLength(key);
int pos = 0;
while (pos < text.length) {
int limit = Math.min(text.length - pos, maxLength);
cipher.init(Cipher.DECRYPT_MODE, key);
cipher.update(text, pos, limit);
pos += limit;
byte[] buffer = cipher.doFinal();
output.write(buffer, 0, buffer.length);
}
return output.toByteArray();
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new IllegalStateException("Cannot decrypt", ex);
}
}
// copied from sun.security.rsa.RSACore.getByteLength(java.math.BigInteger)
public static int getByteLength(RSAKey key) {
int n = key.getModulus().bitLength();
return (n + 7) >> 3;
}
}

View File

@ -0,0 +1,247 @@
/*
* Copyright 2013-2024 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
import javax.crypto.Cipher;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.crypto.keygen.KeyGenerators;
/**
* @author Dave Syer
* @since 6.3
*/
public class RsaSecretEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHolder {
private static final String DEFAULT_ENCODING = "UTF-8";
// The secret for encryption is random (so dictionary attack is not a danger)
private static final String DEFAULT_SALT = "deadbeef";
private final String salt;
private RsaAlgorithm algorithm = RsaAlgorithm.DEFAULT;
private final Charset charset;
private final PublicKey publicKey;
private final PrivateKey privateKey;
private final Charset defaultCharset;
private final boolean gcm;
public RsaSecretEncryptor(RsaAlgorithm algorithm, String salt, boolean gcm) {
this(RsaKeyHelper.generateKeyPair(), algorithm, salt, gcm);
}
public RsaSecretEncryptor(RsaAlgorithm algorithm, String salt) {
this(RsaKeyHelper.generateKeyPair(), algorithm, salt);
}
public RsaSecretEncryptor(RsaAlgorithm algorithm, boolean gcm) {
this(RsaKeyHelper.generateKeyPair(), algorithm, DEFAULT_SALT, gcm);
}
public RsaSecretEncryptor(RsaAlgorithm algorithm) {
this(RsaKeyHelper.generateKeyPair(), algorithm);
}
public RsaSecretEncryptor() {
this(RsaKeyHelper.generateKeyPair());
}
public RsaSecretEncryptor(KeyPair keyPair, RsaAlgorithm algorithm, String salt, boolean gcm) {
this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm, salt, gcm);
}
public RsaSecretEncryptor(KeyPair keyPair, RsaAlgorithm algorithm, String salt) {
this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm, salt, false);
}
public RsaSecretEncryptor(KeyPair keyPair, RsaAlgorithm algorithm) {
this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm);
}
public RsaSecretEncryptor(KeyPair keyPair) {
this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate());
}
public RsaSecretEncryptor(String pemData, RsaAlgorithm algorithm, String salt) {
this(RsaKeyHelper.parseKeyPair(pemData), algorithm, salt);
}
public RsaSecretEncryptor(String pemData, RsaAlgorithm algorithm) {
this(RsaKeyHelper.parseKeyPair(pemData), algorithm);
}
public RsaSecretEncryptor(String pemData) {
this(RsaKeyHelper.parseKeyPair(pemData));
}
public RsaSecretEncryptor(PublicKey publicKey, RsaAlgorithm algorithm, String salt, boolean gcm) {
this(DEFAULT_ENCODING, publicKey, null, algorithm, salt, gcm);
}
public RsaSecretEncryptor(PublicKey publicKey, RsaAlgorithm algorithm, String salt) {
this(DEFAULT_ENCODING, publicKey, null, algorithm, salt, false);
}
public RsaSecretEncryptor(PublicKey publicKey, RsaAlgorithm algorithm) {
this(DEFAULT_ENCODING, publicKey, null, algorithm);
}
public RsaSecretEncryptor(PublicKey publicKey) {
this(DEFAULT_ENCODING, publicKey, null);
}
public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey) {
this(encoding, publicKey, privateKey, RsaAlgorithm.DEFAULT);
}
public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm) {
this(encoding, publicKey, privateKey, algorithm, DEFAULT_SALT, false);
}
public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm,
String salt, boolean gcm) {
this.charset = Charset.forName(encoding);
this.publicKey = publicKey;
this.privateKey = privateKey;
this.defaultCharset = Charset.forName(DEFAULT_ENCODING);
this.algorithm = algorithm;
this.salt = isHex(salt) ? salt : new String(Hex.encode(salt.getBytes(this.defaultCharset)));
this.gcm = gcm;
}
@Override
public String getPublicKey() {
return RsaKeyHelper.encodePublicKey((RSAPublicKey) this.publicKey, "application");
}
@Override
public String encrypt(String text) {
return new String(Base64.getEncoder().encode(encrypt(text.getBytes(this.charset))), this.defaultCharset);
}
@Override
public String decrypt(String encryptedText) {
if (!canDecrypt()) {
throw new IllegalStateException("Encryptor is not configured for decryption");
}
return new String(decrypt(Base64.getDecoder().decode(encryptedText.getBytes(this.defaultCharset))),
this.charset);
}
@Override
public byte[] encrypt(byte[] byteArray) {
return encrypt(byteArray, this.publicKey, this.algorithm, this.salt, this.gcm);
}
@Override
public byte[] decrypt(byte[] encryptedByteArray) {
if (!canDecrypt()) {
throw new IllegalStateException("Encryptor is not configured for decryption");
}
return decrypt(encryptedByteArray, this.privateKey, this.algorithm, this.salt, this.gcm);
}
private static byte[] encrypt(byte[] text, PublicKey key, RsaAlgorithm alg, String salt, boolean gcm) {
byte[] random = KeyGenerators.secureRandom(16).generateKey();
BytesEncryptor aes = gcm ? Encryptors.stronger(new String(Hex.encode(random)), salt)
: Encryptors.standard(new String(Hex.encode(random)), salt);
try {
final Cipher cipher = Cipher.getInstance(alg.getJceName());
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] secret = cipher.doFinal(random);
ByteArrayOutputStream result = new ByteArrayOutputStream(text.length + 20);
writeInt(result, secret.length);
result.write(secret);
result.write(aes.encrypt(text));
return result.toByteArray();
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new IllegalStateException("Cannot encrypt", ex);
}
}
private static void writeInt(ByteArrayOutputStream result, int length) throws IOException {
byte[] data = new byte[2];
data[0] = (byte) ((length >> 8) & 0xFF);
data[1] = (byte) (length & 0xFF);
result.write(data);
}
private static int readInt(ByteArrayInputStream result) throws IOException {
byte[] b = new byte[2];
result.read(b);
return ((b[0] & 0xFF) << 8) | (b[1] & 0xFF);
}
private static byte[] decrypt(byte[] text, PrivateKey key, RsaAlgorithm alg, String salt, boolean gcm) {
ByteArrayInputStream input = new ByteArrayInputStream(text);
ByteArrayOutputStream output = new ByteArrayOutputStream(text.length);
try {
int length = readInt(input);
byte[] random = new byte[length];
input.read(random);
final Cipher cipher = Cipher.getInstance(alg.getJceName());
cipher.init(Cipher.DECRYPT_MODE, key);
String secret = new String(Hex.encode(cipher.doFinal(random)));
byte[] buffer = new byte[text.length - random.length - 2];
input.read(buffer);
BytesEncryptor aes = gcm ? Encryptors.stronger(secret, salt) : Encryptors.standard(secret, salt);
output.write(aes.decrypt(buffer));
return output.toByteArray();
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new IllegalStateException("Cannot decrypt", ex);
}
}
private static boolean isHex(String input) {
try {
Hex.decode(input);
return true;
}
catch (Exception ex) {
return false;
}
}
public boolean canDecrypt() {
return this.privateKey != null;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2013-2024 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 org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Dave Syer
*
*/
public class KeyStoreKeyFactoryTests {
@Test
public void initializeEncryptorFromKeyStore() {
char[] password = "foobar".toCharArray();
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), password);
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("test"));
assertThat(encryptor.canDecrypt()).as("Should be able to decrypt").isTrue();
assertThat(encryptor.decrypt(encryptor.encrypt("foo"))).isEqualTo("foo");
}
@Test
public void initializeEncryptorFromPkcs12KeyStore() {
char[] password = "letmein".toCharArray();
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.pkcs12"), password);
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("mytestkey"));
assertThat(encryptor.canDecrypt()).as("Should be able to decrypt").isTrue();
assertThat(encryptor.decrypt(encryptor.encrypt("foo"))).isEqualTo("foo");
}
@Test
public void initializeEncryptorFromTrustedCertificateInKeyStore() {
char[] password = "foobar".toCharArray();
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), password);
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("testcertificate"));
assertThat(encryptor.canDecrypt()).as("Should not be able to decrypt").isFalse();
assertThat(encryptor.encrypt("foo")).isNotEqualTo("foo");
}
@Test
public void initializeEncryptorFromTrustedCertificateInPkcs12KeyStore() {
char[] password = "letmein".toCharArray();
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.pkcs12"), password);
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("mytestcertificate"));
assertThat(encryptor.canDecrypt()).as("Should not be able to decrypt").isFalse();
assertThat(encryptor.encrypt("foo")).isNotEqualTo("foo");
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2013-2024 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 java.security.KeyPair;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
public class RsaKeyHelperTests {
@Test
public void parsePrivateKey() throws Exception {
// ssh-keygen -m pem -b 1024 -f src/test/resources/fake.pem
String pem = StreamUtils.copyToString(new ClassPathResource("/fake.pem", getClass()).getInputStream(),
StandardCharsets.UTF_8);
KeyPair result = RsaKeyHelper.parseKeyPair(pem);
assertThat(result.getPrivate().getEncoded().length > 0).isTrue();
assertThat(result.getPrivate().getAlgorithm()).isEqualTo("RSA");
}
@Test
public void parseSpaceyKey() throws Exception {
String pem = StreamUtils.copyToString(new ClassPathResource("/spacey.pem", getClass()).getInputStream(),
StandardCharsets.UTF_8);
KeyPair result = RsaKeyHelper.parseKeyPair(pem);
assertThat(result.getPrivate().getEncoded().length > 0).isTrue();
assertThat(result.getPrivate().getAlgorithm()).isEqualTo("RSA");
}
@Test
public void parseBadKey() throws Exception {
// ssh-keygen -m pem -b 1024 -f src/test/resources/fake.pem
String pem = StreamUtils.copyToString(new ClassPathResource("/bad.pem", getClass()).getInputStream(),
StandardCharsets.UTF_8);
try {
RsaKeyHelper.parseKeyPair(pem);
throw new IllegalStateException("Expected IllegalArgumentException");
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage().contains("PEM")).isTrue();
}
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright 2013-2024 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 org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Dave Syer
*
*/
public class RsaRawEncryptorTests {
private RsaRawEncryptor encryptor = new RsaRawEncryptor();
@BeforeEach
public void init() {
LONG_STRING = SHORT_STRING + SHORT_STRING + SHORT_STRING + SHORT_STRING;
for (int i = 0; i < 4; i++) {
LONG_STRING = LONG_STRING + LONG_STRING;
}
}
@Test
public void roundTrip() {
assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void roundTripOeap() {
this.encryptor = new RsaRawEncryptor(RsaAlgorithm.OAEP);
assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void roundTripLongString() {
assertThat(this.encryptor.decrypt(this.encryptor.encrypt(LONG_STRING))).isEqualTo(LONG_STRING);
}
@Test
public void roundTripLongStringOeap() {
this.encryptor = new RsaRawEncryptor(RsaAlgorithm.OAEP);
assertThat(this.encryptor.decrypt(this.encryptor.encrypt(LONG_STRING))).isEqualTo(LONG_STRING);
}
@Test
public void roundTrip2048Key() {
String pemData = "-----BEGIN RSA PRIVATE KEY-----"
+ "MIIEpQIBAAKCAQEA5KHEkCudAHCKIUHKyW6Z8dMyQsKrLbpDe0wDzx9MBARcOoS9"
+ "ZUjzXwK6p/0RM6aCp+b9kkr37QKQ9K/Am13sr0z8Mkn1Q2cvXiL5gbnY1nYGk8/m"
+ "CBX3QEhH2UII4yJsDVx1xmcSorZaWmeNKor7Zl3SZaQpWTvlkMgQKwY8DZL6PPxt"
+ "JRPeKmuUY6B59u5okh1G6Y9OnT2dVxAkqT8WgLHu6StxBmueJ272x2sUWUzoDhnP"
+ "7JRqa7h7t6fml3o3Op1iCywCOFzCIcK6G/oG/WZ7tbBYkwQdDjn/9VMdKkkPufwq"
+ "zt4S75NJygXDwDnNPiTVoaOwrRrL8ahgw6bFCQIDAQABAoIBAECIMHUI+l2fZj2Q"
+ "1m4Ym7cYB320eKCFjHqGsCSMDuarXGTgBp1KA/dzS8ASvAI6I3LEzhm2s1fge420"
+ "9cZksmOgdSa0nVeTDlmhwY8OJ9gQpDagXas2l/066Zy2+M8zbhAvYsbHXQk0MziF"
+ "NeEmLWNtY+9wcINRVrCQ549dSSIDK6UX21oU6d1mrlnF5/bbbdDIM3dKok355jwx"
+ "0HFY0tJIs1zArsBVoz3Ccu1MQEfnxEFM1LLPi5rE6cuHIOBinbD1OQ2R/HM2aukG"
+ "Rk2m6F3wAieJ7zpt5yaHuuIedn8p8m2NVulXAjgkY2oQl3GGiDH/H7eZlrvQRg6E"
+ "D8Bq+ykCgYEA+AfPXVeeVg3Qu0KsNrACek/o92BMY9g3GyPVGULGvq9seoNB86hj"
+ "nXasqngBfTlOfJFiahoEzRBB9hIyo1zMw4x99pR8nGxhR3aU+v8EGftMABGHWsB9"
+ "Jxj4YQH4fhi57iBa72QmNPbu/1o7y3SEe68E5PJ8KY3jc4xos8Vl658CgYEA6/pk"
+ "t6WZII+9lpxQfePQDIlBWAphiQceh995bGXfDmX3vOVmPozix9/fUtF1TeKS/ypw"
+ "u++Qmvj5oMsBVrjCyoOYfHKE2vGrLoEzkX/sPO65IsV00geZZoyCEKEE3USJfY46"
+ "u0hs61oP8HJyLhLiYiGcFTzZ4nEvvEbiM4E/DlcCgYEA6S0OecZhiK08SpAHrvIR"
+ "okN11PqnVkZyqAUr1a+9gI8TAKpdWmA4JlTnRuvDGqLBcsKLLwx+7voVyOyaxpH7"
+ "vutZkHNQIw6Q9co5jS4qAPMLJBVWlq7X+eWzvB9KKeG9Cm1IkD4q3Sg4z79Y75D+"
+ "6/hCNarxp29JIdwior81bikCgYEApp1P+b7pxGzZPvs1df2hCwjqY0BJJ5goPWVT"
+ "dW7kNGVYqz4JmAafpOJz6yTLP2fHxHRxzrBSmKlMj/RmCJZBqv2Jb+zn0zMpW5eM"
+ "EqKQ6WDgxSVH23fUHuz8dMNMDPL0ZPtEirGTfgVEFdCov9FDmGgErZYefVzPiI8/"
+ "7X/HRtcCgYEApQ2YS+0DLPqaM0cC6/6hDr/jmHLFhHaV6DZR7M9HHDnMN2uMlOEa"
+ "RYvXRMBjyQ7LQkwOj6K5k8MVrsDDM5dbekTBgcJMHfM9uViDkB0VPYULORmDJ20N"
+ "MLowIAiSon2B2/isatY80YtFq+bRyvPOzjGvinHN3MU1GH/gFuS0fiw=" + "-----END RSA PRIVATE KEY-----";
RsaRawEncryptor encryptor_2048 = new RsaRawEncryptor(pemData);
assertThat(encryptor_2048.decrypt(encryptor_2048.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void roundTrip4096Key() {
String pemData = "-----BEGIN RSA PRIVATE KEY-----"
+ "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" + "-----END RSA PRIVATE KEY-----";
RsaRawEncryptor encryptor_4096 = new RsaRawEncryptor(pemData);
assertThat(encryptor_4096.decrypt(encryptor_4096.encrypt("encryptor"))).isEqualTo("encryptor");
}
private static final String SHORT_STRING = "Bacon ipsum dolor sit amet tail pork loin pork chop filet mignon flank fatback tenderloin boudin shankle corned beef t-bone short ribs. Meatball capicola ball tip short loin beef ribs shoulder, kielbasa pork chop meatloaf biltong porchetta bresaola t-bone spare ribs. Andouille t-bone sausage ground round frankfurter venison. Ground round meatball chicken ribeye doner tongue porchetta.";
private static String LONG_STRING;
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2013-2024 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.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* @author Dave Syer
*
*/
public class RsaSecretEncryptorTests {
private RsaSecretEncryptor encryptor = new RsaSecretEncryptor();
@BeforeEach
public void init() {
LONG_STRING = SHORT_STRING + SHORT_STRING + SHORT_STRING + SHORT_STRING;
for (int i = 0; i < 4; i++) {
LONG_STRING = LONG_STRING + LONG_STRING;
}
}
@Test
public void roundTripKey() {
PublicKey key = RsaKeyHelper.generateKeyPair().getPublic();
String encoded = RsaKeyHelper.encodePublicKey((RSAPublicKey) key, "application");
assertThat(RsaKeyHelper.parsePublicKey(encoded)).isEqualTo(key);
}
@Test
public void roundTrip() {
assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void roundTripWithSalt() {
this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, "somesalt");
assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void roundTripWithHexSalt() {
this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, "beefea");
assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void roundTripWithLongSalt() {
this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, "somesaltsomesaltsomesaltsomesaltsomesalt");
assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void roundTripOaep() {
this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP);
assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void roundTripOaepGcm() {
this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, true);
assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void roundTripWithMixedAlgorithm() {
RsaSecretEncryptor oaep = new RsaSecretEncryptor(RsaAlgorithm.OAEP);
assertThatIllegalStateException().isThrownBy(() -> oaep.decrypt(this.encryptor.encrypt("encryptor")));
}
@Test
public void roundTripWithMixedSalt() {
RsaSecretEncryptor other = new RsaSecretEncryptor(this.encryptor.getPublicKey(), RsaAlgorithm.DEFAULT, "salt");
assertThatIllegalStateException().isThrownBy(() -> this.encryptor.decrypt(other.encrypt("encryptor")));
}
@Test
public void roundTripWithPublicKeyEncryption() {
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(this.encryptor.getPublicKey());
RsaSecretEncryptor decryptor = this.encryptor;
assertThat(decryptor.decrypt(encryptor.encrypt("encryptor"))).isEqualTo("encryptor");
}
@Test
public void publicKeyCannotDecrypt() {
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(this.encryptor.getPublicKey());
assertThat(encryptor.canDecrypt()).as("Encryptor schould not be able to decrypt").isFalse();
assertThatIllegalStateException().isThrownBy(() -> encryptor.decrypt(encryptor.encrypt("encryptor")));
}
@Test
public void roundTripLongString() {
assertThat(this.encryptor.decrypt(this.encryptor.encrypt(LONG_STRING))).isEqualTo(LONG_STRING);
}
private static final String SHORT_STRING = "Bacon ipsum dolor sit amet tail pork loin pork chop filet mignon flank fatback tenderloin boudin shankle corned beef t-bone short ribs. Meatball capicola ball tip short loin beef ribs shoulder, kielbasa pork chop meatloaf biltong porchetta bresaola t-bone spare ribs. Andouille t-bone sausage ground round frankfurter venison. Ground round meatball chicken ribeye doner tongue porchetta.";
private static String LONG_STRING;
}

View File

@ -0,0 +1,2 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwClFgrRa/PUHPIJr9gvIPL6g6Rjp/TVZmVNOf2fL96DYbkj5

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDMWnfaQ0yLFXelprq2S8UurnaGvxFNUdbmTyJeycem5vGLycEY
T4KcdVCTU5491cjbk5GcHjoj2efRSO0y0aXIlUJpLofDdML/SuGLZWp/GbEv978M
pZIztK8iaIm7D/D7by8aws1RJyD9T+lZDAGY7eFfMp0EQyHOcEL0NGFLuwIDAQAB
AoGAWwC6uO8ZaiKwOouqQD4z3FsDG3SA/v7ABaYd9zpCd9gGnyrEm8/kqUoxDLrD
EGRg4y+vO2fWmlqSuoeQYf4spf+vi2di+mGIb6nGe7TpMLPa7lFLOSQHZRx5M5H6
JDhfhAHlKmF9gLGvDHbpyErzn5YXjcu0PoFiNC1y445D8iECQQDvJzkGbJ9l9vb0
oRyGXRDpddUcVMECLLB9NKmTl/zKy/qVPD+zYNoi87ePBJFbgmAXRjhhTk2uSBRP
NtVaMoXLAkEA2r+ugzjsLZQIYz/9gxdzdbKWDgpSPbhKCR4bOmrDgJMcOVjtwW+n
+liaX6zwI0QEgCAWLzCbbYDmj3kJrRwT0QJAaowg/dm7EmR7FfYJjVs9Q6X5skuY
Se27G60wt88JExjZpU9YWgSWaugGKbOxRwHI6dWhHMkUFseKNNiLKUpFDQJALIGP
ahdsxiE2S6s7Uy60SSAas6SZ8wDJ320GsS4DtOc5eNmFFjQ3gxH/5rNy8FnoaIEe
wl8rYG43er1voI7z4QJAB4qaqBo7eeiRgnUVIccaSZkNIMSrZ9QUjVFRgfLwAXDO
Ae+t6V+eB0oaIXczA+BLj3Oe6D3iHRGHrxGlcvDdHw==
-----END RSA PRIVATE KEY-----

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,25 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwClFgrRa/PUHPIJr9gvIPL6g6Rjp/TVZmVNOf2fL96DYbkj5
4YbrwfKwjoTjk1M6gLQpOA4Blocx6zN5OnICnVGlVM9xymWxTxxCfc2tE2Fai9I1wchULCChhwm/UU5ZNi3KpXinlyamSYw+lMQkZ8gTXCgOEvs2j9E1quF4pvy1BZKvbD8tUnUQlyiKRnI6gOxQL8B6OAYPRdaa9FVNmrs1B4eDPG918L2f1pT090P1n+tw
iejNgQvtSD78/A88qt89OhzscsufALTrBjycn89kkfBd0zbVLF0W6+ZVLZrf97/y
LCoGSCcZL9LFPNvNqxOnleviDco7aOs4stQ9jQIDAQABAoIBAQC1TbthyN0YUe+T
7dIDAbbZaVrU00biOtXgzjMADmTprP7Hf18UpIIIKfzfWw6FUD+gc1t4oe5pogE9
UwGMXUmOORxu2pMYTb5vT9CEdexYnsAZsCo8PdD9GYSNrmquQef2MFpEqYQmHrdC
KWpaXn2i1ak+iCRPUGp4YwHpynZVxfE8z/AIsPn6NPDh6SnCXb1rTgQe2UCfXm93
UJe5F/OR2kQi5KFO+dxLmCOBCwr6SGCLH+VotGpuxCVRUd9sJ/d4QpDZEgjuf7Ug
eQHfgMDS/tc09B9rl0dwKnEa31kcQ9X9KLkKP+w0Pqhh0Emny20eg9jS6XNayg61
p/LQtW9BAoGBAO5veKMIcXfZmuh11WIIhdhLKkNtYyt5NDmrV8/IVScLFvjB0ftt
8PAtXo/ekOHkyITyIumQ9l4VCvacNw7DyV9FYk4WvrvVYOCL8aZi+O5+12NT67eO
Rr/voGlRoV05X7+inc90qbbYJ8lRmLSqvzmsm98mkuhw/FKGRhVZIfAJAoGBAM5R
I5vK6cJxOwXQOEGOd5/8B9JMFXyuendXo/N2/NxSQsbx4pc3v2rv/eGJYaY7Nx/y
2M/vdWYkpG59PAS3k2TrCA/0SGmyVqY+c8BomKisU5VaBlIPfGuec9tDPgWCp8Ur
3Jjt/2sVoa0vMkqymUqMb9HyH9tdI9oyh7EOOrplAoGAR6DlNNUMgVy11K/Rcqns
y5WJFMh/ykeXENwQfTNJoXkLZZ+UXVwhzYVTqxTJoZMBSi8TnecWnBzmNj+nqp/W
lvBZH+xlUDhB6jMgXUPOVJd2TTigz3vGdVKfdgQ33bGmugM4NWJuuacmDKyem2fQ
GptoGBmWeI24v3HnC/LC50ECgYAz0iN8hRnz0db+Xc9TgAJB997LDnszJuvxv9yZ
UWCvwiWtrKG6U7FLnd4J4STayPLOnoOgrsexETEP43rIwIdQCMysnTH3AmlLNlKC
mIMHksknsUX3JJaevVziTOBuJ+QV3S96ZgUKk5NZWYprQrLIC8AmXodr5NgVfS2h
5i4QFQKBgFfbYHiMw5AAUQrBNkrAjLd1wIaO/6qS3w4OsCWKowhfaJLEXAbIRV7s
vAtgtlCovdasVj4RRLXFf+73naVTQjBZI+3jWHHyFk3+Zy86mQCSGv9WuDVV1IhS
h8InTVvK8wgdgX7qiw3pvU0roqNW4/j4j8OqJO3Zt4KO2iX8htsO
-----END RSA PRIVATE KEY-----

View File

@ -11,3 +11,7 @@ Below are the highlights of the release.
== CAS
- https://github.com/spring-projects/spring-security/pull/14193[gh-14193] - Added support for CAS Gateway Authentication
== Crypto
- https://github.com/spring-projects/spring-security/issues/14202[gh-14202] - Migrated spring-security-rsa into spring-security-crypto