Support encrypted PKCS8 private keys in SSL bundles

Properties `ssl.bundle.pem.mybundle.keystore.private-key-password`
and `ssl.bundle.pem.mybundle.truststore.private-key-password` have
been added for configuring the password required to decrypt an
encrypted private key.

Only PKCS8 private keys with encryption are supported. PKCS1 and EC
private keys with encryption are much more complex to decrypt, and
are not supported.

Fixes gh-35652
This commit is contained in:
Scott Frederick 2023-06-06 13:47:17 -05:00
parent 7fcfcadfc3
commit 767ec4e22e
12 changed files with 355 additions and 17 deletions

View File

@ -66,6 +66,11 @@ public class PemSslBundleProperties extends SslBundleProperties {
*/
String privateKey;
/**
* Password used to decrypt an encrypted private key.
*/
String privateKeyPassword;
public String getType() {
return this.type;
}
@ -90,6 +95,14 @@ public class PemSslBundleProperties extends SslBundleProperties {
this.privateKey = privateKey;
}
public String getPrivateKeyPassword() {
return this.privateKeyPassword;
}
public void setPrivateKeyPassword(String privateKeyPassword) {
this.privateKeyPassword = privateKeyPassword;
}
}
}

View File

@ -113,7 +113,8 @@ public final class PropertiesSslBundle implements SslBundle {
}
private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) {
return new PemSslStoreDetails(properties.getType(), properties.getCertificate(), properties.getPrivateKey());
return new PemSslStoreDetails(properties.getType(), properties.getCertificate(), properties.getPrivateKey(),
properties.getPrivateKeyPassword());
}
private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties) {

View File

@ -0,0 +1,92 @@
/*
* Copyright 2012-2023 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.boot.autoconfigure.ssl;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.boot.ssl.SslBundle;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link PropertiesSslBundle}.
*
* @author Scott Frederick
*/
class PropertiesSslBundleTests {
@Test
void pemPropertiesAreMappedToSslBundle() {
PemSslBundleProperties properties = new PemSslBundleProperties();
properties.getKey().setAlias("alias");
properties.getKey().setPassword("secret");
properties.getOptions().setCiphers(Set.of("cipher1", "cipher2", "cipher3"));
properties.getOptions().setEnabledProtocols(Set.of("protocol1", "protocol2"));
properties.getKeystore().setCertificate("cert1.pem");
properties.getKeystore().setPrivateKey("key1.pem");
properties.getKeystore().setPrivateKeyPassword("keysecret1");
properties.getKeystore().setType("PKCS12");
properties.getTruststore().setCertificate("cert2.pem");
properties.getTruststore().setPrivateKey("key2.pem");
properties.getTruststore().setPrivateKeyPassword("keysecret2");
properties.getTruststore().setType("JKS");
SslBundle sslBundle = PropertiesSslBundle.get(properties);
assertThat(sslBundle.getKey().getAlias()).isEqualTo("alias");
assertThat(sslBundle.getKey().getPassword()).isEqualTo("secret");
assertThat(sslBundle.getOptions().getCiphers()).containsExactlyInAnyOrder("cipher1", "cipher2", "cipher3");
assertThat(sslBundle.getOptions().getEnabledProtocols()).containsExactlyInAnyOrder("protocol1", "protocol2");
assertThat(sslBundle.getStores()).isNotNull();
assertThat(sslBundle.getStores()).extracting("keyStoreDetails")
.extracting("certificate", "privateKey", "privateKeyPassword", "type")
.containsExactly("cert1.pem", "key1.pem", "keysecret1", "PKCS12");
assertThat(sslBundle.getStores()).extracting("trustStoreDetails")
.extracting("certificate", "privateKey", "privateKeyPassword", "type")
.containsExactly("cert2.pem", "key2.pem", "keysecret2", "JKS");
}
@Test
void jksPropertiesAreMappedToSslBundle() {
JksSslBundleProperties properties = new JksSslBundleProperties();
properties.getKey().setAlias("alias");
properties.getKey().setPassword("secret");
properties.getOptions().setCiphers(Set.of("cipher1", "cipher2", "cipher3"));
properties.getOptions().setEnabledProtocols(Set.of("protocol1", "protocol2"));
properties.getKeystore().setLocation("cert1.p12");
properties.getKeystore().setPassword("secret1");
properties.getKeystore().setProvider("provider1");
properties.getKeystore().setType("JKS");
properties.getTruststore().setLocation("cert2.jks");
properties.getTruststore().setPassword("secret2");
properties.getTruststore().setProvider("provider2");
properties.getTruststore().setType("PKCS12");
SslBundle sslBundle = PropertiesSslBundle.get(properties);
assertThat(sslBundle.getKey().getAlias()).isEqualTo("alias");
assertThat(sslBundle.getKey().getPassword()).isEqualTo("secret");
assertThat(sslBundle.getOptions().getCiphers()).containsExactlyInAnyOrder("cipher1", "cipher2", "cipher3");
assertThat(sslBundle.getOptions().getEnabledProtocols()).containsExactlyInAnyOrder("protocol1", "protocol2");
assertThat(sslBundle.getStores()).isNotNull();
assertThat(sslBundle.getStores()).extracting("keyStoreDetails")
.extracting("location", "password", "provider", "type")
.containsExactly("cert1.p12", "secret1", "provider1", "JKS");
assertThat(sslBundle.getStores()).extracting("trustStoreDetails")
.extracting("location", "password", "provider", "type")
.containsExactly("cert2.jks", "secret2", "provider2", "PKCS12");
}
}

View File

@ -18,6 +18,7 @@ package org.springframework.boot.ssl.pem;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
@ -27,10 +28,18 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.springframework.util.Assert;
/**
* Parser for PKCS private key files in PEM format.
*
@ -48,18 +57,27 @@ final class PemPrivateKeyParser {
private static final String PKCS8_FOOTER = "-+END\\s+PRIVATE\\s+KEY[^-]*-+";
private static final String PKCS8_ENCRYPTED_HEADER = "-+BEGIN\\s+ENCRYPTED\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+";
private static final String PKCS8_ENCRYPTED_FOOTER = "-+END\\s+ENCRYPTED\\s+PRIVATE\\s+KEY[^-]*-+";
private static final String EC_HEADER = "-+BEGIN\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+";
private static final String EC_FOOTER = "-+END\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+";
private static final String BASE64_TEXT = "([a-z0-9+/=\\r\\n]+)";
public static final int BASE64_TEXT_GROUP = 1;
private static final List<PemParser> PEM_PARSERS;
static {
List<PemParser> parsers = new ArrayList<>();
parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, PemPrivateKeyParser::createKeySpecForPkcs1, "RSA"));
parsers.add(new PemParser(EC_HEADER, EC_FOOTER, PemPrivateKeyParser::createKeySpecForEc, "EC"));
parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, PKCS8EncodedKeySpec::new, "RSA", "EC", "DSA", "Ed25519"));
parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, PemPrivateKeyParser::createKeySpecForPkcs8, "RSA", "EC",
"DSA", "Ed25519"));
parsers.add(new PemParser(PKCS8_ENCRYPTED_HEADER, PKCS8_ENCRYPTED_FOOTER,
PemPrivateKeyParser::createKeySpecForPkcs8Encrypted, "RSA", "EC", "DSA", "Ed25519"));
PEM_PARSERS = Collections.unmodifiableList(parsers);
}
@ -81,11 +99,11 @@ final class PemPrivateKeyParser {
private PemPrivateKeyParser() {
}
private static PKCS8EncodedKeySpec createKeySpecForPkcs1(byte[] bytes) {
private static PKCS8EncodedKeySpec createKeySpecForPkcs1(byte[] bytes, String password) {
return createKeySpecForAlgorithm(bytes, RSA_ALGORITHM, null);
}
private static PKCS8EncodedKeySpec createKeySpecForEc(byte[] bytes) {
private static PKCS8EncodedKeySpec createKeySpecForEc(byte[] bytes, String password) {
return createKeySpecForAlgorithm(bytes, EC_ALGORITHM, EC_PARAMETERS);
}
@ -106,18 +124,37 @@ final class PemPrivateKeyParser {
}
}
private static PKCS8EncodedKeySpec createKeySpecForPkcs8(byte[] bytes, String password) {
return new PKCS8EncodedKeySpec(bytes);
}
private static PKCS8EncodedKeySpec createKeySpecForPkcs8Encrypted(byte[] bytes, String password) {
return Pkcs8PrivateKeyDecryptor.decrypt(bytes, password);
}
/**
* Parse a private key from the specified string.
* @param key the private key to parse
* @return the parsed private key
*/
static PrivateKey parse(String key) {
return parse(key, null);
}
/**
* Parse a private key from the specified string, using the provided password for
* decryption if necessary.
* @param key the private key to parse
* @param password the password used to decrypt an encrypted private key
* @return the parsed private key
*/
static PrivateKey parse(String key, String password) {
if (key == null) {
return null;
}
try {
for (PemParser pemParser : PEM_PARSERS) {
PrivateKey privateKey = pemParser.parse(key);
PrivateKey privateKey = pemParser.parse(key, password);
if (privateKey != null) {
return privateKey;
}
@ -136,20 +173,20 @@ final class PemPrivateKeyParser {
private final Pattern pattern;
private final Function<byte[], PKCS8EncodedKeySpec> keySpecFactory;
private final BiFunction<byte[], String, PKCS8EncodedKeySpec> keySpecFactory;
private final String[] algorithms;
PemParser(String header, String footer, Function<byte[], PKCS8EncodedKeySpec> keySpecFactory,
PemParser(String header, String footer, BiFunction<byte[], String, PKCS8EncodedKeySpec> keySpecFactory,
String... algorithms) {
this.pattern = Pattern.compile(header + BASE64_TEXT + footer, Pattern.CASE_INSENSITIVE);
this.algorithms = algorithms;
this.keySpecFactory = keySpecFactory;
this.algorithms = algorithms;
}
PrivateKey parse(String text) {
PrivateKey parse(String text, String password) {
Matcher matcher = this.pattern.matcher(text);
return (!matcher.find()) ? null : parse(decodeBase64(matcher.group(1)));
return (!matcher.find()) ? null : parse(decodeBase64(matcher.group(BASE64_TEXT_GROUP)), password);
}
private static byte[] decodeBase64(String content) {
@ -157,9 +194,9 @@ final class PemPrivateKeyParser {
return Base64.getDecoder().decode(contentBytes);
}
private PrivateKey parse(byte[] bytes) {
private PrivateKey parse(byte[] bytes, String password) {
try {
PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes);
PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes, password);
for (String algorithm : this.algorithms) {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
try {
@ -251,4 +288,34 @@ final class PemPrivateKeyParser {
}
static class Pkcs8PrivateKeyDecryptor {
public static final String PBES2_ALGORITHM = "PBES2";
static PKCS8EncodedKeySpec decrypt(byte[] bytes, String password) {
Assert.notNull(password, "Password is required for an encrypted private key");
try {
EncryptedPrivateKeyInfo keyInfo = new EncryptedPrivateKeyInfo(bytes);
AlgorithmParameters algorithmParameters = keyInfo.getAlgParameters();
String encryptionAlgorithm = getEncryptionAlgorithm(algorithmParameters, keyInfo.getAlgName());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptionAlgorithm);
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(password.toCharArray()));
Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, key, algorithmParameters);
return keyInfo.getKeySpec(cipher);
}
catch (IOException | GeneralSecurityException ex) {
throw new IllegalArgumentException("Error decrypting private key", ex);
}
}
private static String getEncryptionAlgorithm(AlgorithmParameters algParameters, String algName) {
if (algParameters != null && PBES2_ALGORITHM.equals(algName)) {
return algParameters.toString();
}
return algName;
}
}
}

View File

@ -84,14 +84,14 @@ public class PemSslStoreBundle implements SslStoreBundle {
return null;
}
try {
Assert.notNull(details.certificate(), "CertificateContent must not be null");
Assert.notNull(details.certificate(), "Certificate content must not be null");
String type = (!StringUtils.hasText(details.type())) ? KeyStore.getDefaultType() : details.type();
KeyStore store = KeyStore.getInstance(type);
store.load(null);
String certificateContent = PemContent.load(details.certificate());
String privateKeyContent = PemContent.load(details.privateKey());
X509Certificate[] certificates = PemCertificateParser.parse(certificateContent);
PrivateKey privateKey = PemPrivateKeyParser.parse(privateKeyContent);
PrivateKey privateKey = PemPrivateKeyParser.parse(privateKeyContent, details.privateKeyPassword());
addCertificates(store, certificates, privateKey);
return store;
}

View File

@ -30,11 +30,16 @@ import org.springframework.util.StringUtils;
* that can be loaded by {@link ResourceUtils#getURL})
* @param privateKey the private key content (either the PEM content itself or something
* that can be loaded by {@link ResourceUtils#getURL})
* @param privateKeyPassword a password used to decrypt an encrypted private key
* @author Scott Frederick
* @author Phillip Webb
* @since 3.1.0
*/
public record PemSslStoreDetails(String type, String certificate, String privateKey) {
public record PemSslStoreDetails(String type, String certificate, String privateKey, String privateKeyPassword) {
public PemSslStoreDetails(String type, String certificate, String privateKey) {
this(type, certificate, privateKey, null);
}
/**
* Return a new {@link PemSslStoreDetails} instance with a new private key.
@ -42,7 +47,16 @@ public record PemSslStoreDetails(String type, String certificate, String private
* @return a new {@link PemSslStoreDetails} instance
*/
public PemSslStoreDetails withPrivateKey(String privateKey) {
return new PemSslStoreDetails(this.type, this.certificate, privateKey);
return new PemSslStoreDetails(this.type, this.certificate, privateKey, this.privateKeyPassword);
}
/**
* Return a new {@link PemSslStoreDetails} instance with a new private key password.
* @param password the new private key password
* @return a new {@link PemSslStoreDetails} instance
*/
public PemSslStoreDetails withPrivateKeyPassword(String password) {
return new PemSslStoreDetails(this.type, this.certificate, this.privateKey, password);
}
boolean isEmpty() {

View File

@ -84,6 +84,47 @@ class PemPrivateKeyParserTests {
assertThatIllegalStateException().isThrownBy(() -> PemPrivateKeyParser.parse(read("test-banner.txt")));
}
@Test
void parsePkcs8EncryptedRsaKeyFile() throws Exception {
// created with:
// openssl genpkey -aes-256-cbc -algorithm RSA \
// -pkeyopt rsa_keygen_bits:4096 -out key-rsa-encrypted.key
PrivateKey privateKey = PemPrivateKeyParser.parse(read("ssl/pkcs8/key-rsa-encrypted.pem"), "test");
assertThat(privateKey).isNotNull();
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
assertThat(privateKey.getAlgorithm()).isEqualTo("RSA");
}
@Test
void parsePkcs8EncryptedEcKeyFile() throws Exception {
// created with:
// openssl genpkey -aes-256-cbc -algorithm EC \
// -pkeyopt ec_paramgen_curve:prime256v1 -out key-ec-encrypted.key
PrivateKey privateKey = PemPrivateKeyParser.parse(read("ssl/pkcs8/key-ec-encrypted.pem"), "test");
assertThat(privateKey).isNotNull();
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
assertThat(privateKey.getAlgorithm()).isEqualTo("EC");
}
@Test
void failParsingPkcs1EncryptedKeyFile() throws Exception {
// created with:
// openssl genrsa -aes-256-cbc -out key-rsa-encrypted.pem
assertThatIllegalStateException()
.isThrownBy(() -> PemPrivateKeyParser.parse(read("ssl/pkcs1/key-rsa-encrypted.pem"), "test"))
.withMessageContaining("Unrecognized private key format");
}
@Test
void failParsingEcEncryptedKeyFile() throws Exception {
// created with:
// openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -out
// key-ec-prime256v1-encrypted.pem
assertThatIllegalStateException()
.isThrownBy(() -> PemPrivateKeyParser.parse(read("ssl/ec/key-ec-prime256v1-encrypted.pem"), "test"))
.withMessageContaining("Unrecognized private key format");
}
private String read(String path) throws IOException {
return new ClassPathResource(path).getContentAsString(StandardCharsets.UTF_8);
}

View File

@ -63,6 +63,17 @@ class PemSslStoreBundleTests {
assertThat(bundle.getTrustStore()).isNull();
}
@Test
void whenHasKeyStoreDetailsCertAndEncryptedKey() {
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
.withPrivateKey("classpath:ssl/pkcs8/key-rsa-encrypted.pem")
.withPrivateKeyPassword("test");
PemSslStoreDetails trustStoreDetails = null;
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("ssl"));
assertThat(bundle.getTrustStore()).isNull();
}
@Test
void whenHasKeyStoreDetailsAndTrustStoreDetailsWithoutKey() {
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")

View File

@ -0,0 +1,8 @@
-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,3F26BBC4C7A6F3B5B3A5C03C7CC59B33
rp4qC+n+qIG+WKJp4BHQHk5z0oraaaLZvoVK5glESGx5IcR3mCsN7tdg2aZ7yEk+
HnT4nQuM3R5pv248cmK0xDUje8N7FLe8lixVnEyQx3JdZfGdauowt9yaxL3AJypX
idWxNrxz1xff5RSMI6+PFv2SQpG0l794EpOjZxOUABM=
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,2036BA8802B364C6BE415A48E1AC9966
0euP/daGIbl798XGc2Tc6u6NmnCl8C6hE0a2grO+wbvFEa13IT4hNVNTzqahfLb8
STF9LXdvPbz9HybyWnG8KNm5sqsSY1omsHQI38khhwuhpHuWfjb62TqWobwrAmAU
qh2+kjc7upzW48axJLRQ69vfE4zKczZ4Sqv8qImPz9SiXPeaXinHf/wKfgXZZOvr
6A8nCykLglVrFbrZWydqKrTM9w/LTXsn0gNpDmrfZERDgWnabKnjYidHkXdgAoFy
ulsBjKvj2hJFtnJHTkCPXj6XAj18cwUWsN7inFJR5wqjIWXuLqfv/fNbK9lPzjAk
vMTEVWwynekvXFsSvnHAyEJfmRCNF6DOE+5VwjEROCFq8xceK4fz4T3BHC7XSgis
2ofBZwjCAJ0pP6SdRpbbZ0wlXH09wqDDTZ2vWD6bEG7hO9VuP/aO76MIWydLa78z
bDb2R6MnYKmYEInPzwfU4H66Z3/cz8orfYLlnF55DRd4rO+fJm6Y/XY14ac3x0S+
P67s5e0WbdImagUsOxGNBjgOr84lwmtk/LoVd5nKho8JyQw3V7tiyrsKsM1lAXdS
TaJXKRnN6LbgC8d+Tgfjc/qMUdVqLlN8zATIa6E4sc861DDiJsneXuFm43pHc/ja
0sxSifBKgLGenj8xY5ANEfrYnXKrzaQemUnMzd+zan6OS02I+e8WTQevESt1j0D8
7mgLJ9W2SfNNfiEd5OKar21lLNOc3POaFn7M73zL/gyhTROipPR3fpV+08k27UKl
nzGhFtRwQieXl34QjM0JrHokKKfv8FsAJsbrGnz2/wcm6jJGmrj5VVsogMKc90v4
ZTM44NAKymM0UTuIp/rKb/UUVYDpl9VWDegWh2+XKX8io31ENrDMcvQTJ/mzNCuZ
SINrAeMbVD6W5L0i7THEt32YsDmbFsBaEJBlNXlUNBa/NCK0pjwAn5AYOFUvEqBC
oZUEleUMU6Q3TKI287o37euw3No6jo5VdPULlrwHsZAjmq9EUvRCgMoIEPdxv5XG
a4PljE7DQLlk6G9d+gjRzClLFfkadSTbtH9o2011RtWUKP4dcRuSOcBm64xVIO6J
rhe38sE3yzHvLUM/mvLsEM8B0AHl85nrEstlbBzftXMo2CAJ2Gs9c61PYfft+xiT
pRdHx4HB1P+kfHa96ayvAytOexHih2iVKVG5CchOr5tbWmkhXVE5cZAKzvczDYFd
YvniHNiqt6LO8EbJOzz+Yxessmd0zBXj/rjxTMdGwaRjiFI0BIguYpvmGoMS8+bp
spRj2DMtqjNZz68BEEfgKQwDHPCTblYSR+3Uw4sja608sflqQ8rTmOwbfta3SNS8
Kq/zzWfYzarQ8hAk5n/H1Jm6AQdvcptyMjuF8FAiMnvt8xDCBRMD3xY2BHb/tcs9
dBWBxhb76CKIrV3pzm9gGhhZ32Ndq9KmmE+bWCYyvrLxvPJxODfM7X5XamPmG5SH
wEKSbp0wPF0b6vyxt5M72OYnU8UnxYWu6PlbVvczfWEu9fIw/oIN/kDT/QUojTyt
wBgzVSTSNFaDtVXJw10IWQgsgWdNY5XueHKH060P5g+14woxU8i3TboNd+tC3hRl
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,7 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAjZ2eXtLFXLdgICCAAw
DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEIlOTfeoLwq9Bs9TFtw7VSMEgZAU
RO9W+K3vvzRG4sACrcFaB7PMhv3+HUOcPFf09QHa7aSJMWsb8EXGFBK4xlFhkK0/
N0C8sRtH2N1JdYj9hiwS7I8WyybaR2W0ZALNR57iLz3WsOGQ7nVECFprElboqnSW
BtjkKTD9pz3xX/6cMkDV/2WqbS6Y/dzWJTH8yTTqjzTjbs4vguEK1Io6dlIRVqA=
-----END ENCRYPTED PRIVATE KEY-----

View File

@ -0,0 +1,54 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
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
-----END ENCRYPTED PRIVATE KEY-----