mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-01 09:42:13 +00:00
Migrate spring-security-rsa into spring-security-crypto
Closes gh-14202
This commit is contained in:
parent
45f8ab3401
commit
6f7b9bbfde
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
2
crypto/src/test/resources/bad.pem
Normal file
2
crypto/src/test/resources/bad.pem
Normal file
@ -0,0 +1,2 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAwClFgrRa/PUHPIJr9gvIPL6g6Rjp/TVZmVNOf2fL96DYbkj5
|
15
crypto/src/test/resources/fake.pem
Normal file
15
crypto/src/test/resources/fake.pem
Normal 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-----
|
BIN
crypto/src/test/resources/keystore.jks
Normal file
BIN
crypto/src/test/resources/keystore.jks
Normal file
Binary file not shown.
BIN
crypto/src/test/resources/keystore.pkcs12
Normal file
BIN
crypto/src/test/resources/keystore.pkcs12
Normal file
Binary file not shown.
25
crypto/src/test/resources/spacey.pem
Normal file
25
crypto/src/test/resources/spacey.pem
Normal 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-----
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user