summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-12-21 12:34:49 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-12-21 12:34:49 +0700
commit3c508655db514af6702bb51be63dc0b3d176e11b (patch)
tree2ae0176a5616ecc1b0b30a85a29f2805dc4b5241
parentAesWrap: update from bc-java (diff)
downloadBouncyCastle.NET-ed25519-3c508655db514af6702bb51be63dc0b3d176e11b.tar.xz
Span-based alternatives to char[]
-rw-r--r--crypto/src/cmp/ProtectedPkiMessage.cs16
-rw-r--r--crypto/src/cms/CMSPBEKey.cs24
-rw-r--r--crypto/src/cms/PKCS5Scheme2PBEKey.cs14
-rw-r--r--crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs14
-rw-r--r--crypto/src/crmf/CertificateRequestMessageBuilder.cs6
-rw-r--r--crypto/src/crmf/PKMacBuilder.cs48
-rw-r--r--crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs38
-rw-r--r--crypto/src/crypto/PbeParametersGenerator.cs43
-rw-r--r--crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs17
-rw-r--r--crypto/src/openssl/MiscPemGenerator.cs69
-rw-r--r--crypto/src/openssl/PEMUtilities.cs111
-rw-r--r--crypto/src/security/JksStore.cs343
-rw-r--r--crypto/src/util/Strings.cs22
13 files changed, 673 insertions, 92 deletions
diff --git a/crypto/src/cmp/ProtectedPkiMessage.cs b/crypto/src/cmp/ProtectedPkiMessage.cs
index 8c9a4b152..df4c45143 100644
--- a/crypto/src/cmp/ProtectedPkiMessage.cs
+++ b/crypto/src/cmp/ProtectedPkiMessage.cs
@@ -119,6 +119,22 @@ namespace Org.BouncyCastle.Cmp
             return Arrays.FixedTimeEquals(result.Collect(), m_pkiMessage.Protection.GetBytes());
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual bool Verify(PKMacBuilder pkMacBuilder, ReadOnlySpan<char> password)
+        {
+            if (!CmpObjectIdentifiers.passwordBasedMac.Equals(m_pkiMessage.Header.ProtectionAlg.Algorithm))
+                throw new InvalidOperationException("protection algorithm is not mac based");
+
+            PbmParameter parameter = PbmParameter.GetInstance(m_pkiMessage.Header.ProtectionAlg.Parameters);
+
+            pkMacBuilder.SetParameters(parameter);
+
+            IBlockResult result = Process(pkMacBuilder.Build(password).CreateCalculator());
+
+            return Arrays.FixedTimeEquals(result.Collect(), m_pkiMessage.Protection.GetBytes());
+        }
+#endif
+
         private TResult Process<TResult>(IStreamCalculator<TResult> streamCalculator)
         {
             Asn1EncodableVector avec = new Asn1EncodableVector();
diff --git a/crypto/src/cms/CMSPBEKey.cs b/crypto/src/cms/CMSPBEKey.cs
index 78360c2cd..4b3e542ee 100644
--- a/crypto/src/cms/CMSPBEKey.cs
+++ b/crypto/src/cms/CMSPBEKey.cs
@@ -45,7 +45,29 @@ namespace Org.BouncyCastle.Cms
 			this.iterationCount = kdfParams.IterationCount.IntValue;
 		}
 
-		~CmsPbeKey()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public CmsPbeKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> salt, int iterationCount)
+        {
+			this.password = password.ToArray();
+			this.salt = salt.ToArray();
+            this.iterationCount = iterationCount;
+        }
+
+        public CmsPbeKey(ReadOnlySpan<char> password, AlgorithmIdentifier keyDerivationAlgorithm)
+        {
+            if (!keyDerivationAlgorithm.Algorithm.Equals(PkcsObjectIdentifiers.IdPbkdf2))
+                throw new ArgumentException("Unsupported key derivation algorithm: "
+                    + keyDerivationAlgorithm.Algorithm);
+
+            Pbkdf2Params kdfParams = Pbkdf2Params.GetInstance(keyDerivationAlgorithm.Parameters.ToAsn1Object());
+
+			this.password = password.ToArray();
+            this.salt = kdfParams.GetSalt();
+            this.iterationCount = kdfParams.IterationCount.IntValue;
+        }
+#endif
+
+        ~CmsPbeKey()
 		{
 			Array.Clear(this.password, 0, this.password.Length);
 		}
diff --git a/crypto/src/cms/PKCS5Scheme2PBEKey.cs b/crypto/src/cms/PKCS5Scheme2PBEKey.cs
index 6606d5c45..78238292d 100644
--- a/crypto/src/cms/PKCS5Scheme2PBEKey.cs
+++ b/crypto/src/cms/PKCS5Scheme2PBEKey.cs
@@ -29,7 +29,19 @@ namespace Org.BouncyCastle.Cms
 		{
 		}
 
-		internal override KeyParameter GetEncoded(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Pkcs5Scheme2PbeKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> salt, int iterationCount)
+            : base(password, salt, iterationCount)
+        {
+        }
+
+        public Pkcs5Scheme2PbeKey(ReadOnlySpan<char> password, AlgorithmIdentifier keyDerivationAlgorithm)
+            : base(password, keyDerivationAlgorithm)
+        {
+        }
+#endif
+
+        internal override KeyParameter GetEncoded(
 			string algorithmOid)
 		{
 			Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
diff --git a/crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs b/crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs
index e2a09b760..68eff7b44 100644
--- a/crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs
+++ b/crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs
@@ -29,7 +29,19 @@ namespace Org.BouncyCastle.Cms
 		{
 		}
 
-		internal override KeyParameter GetEncoded(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Pkcs5Scheme2Utf8PbeKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> salt, int iterationCount)
+            : base(password, salt, iterationCount)
+        {
+        }
+
+        public Pkcs5Scheme2Utf8PbeKey(ReadOnlySpan<char> password, AlgorithmIdentifier keyDerivationAlgorithm)
+            : base(password, keyDerivationAlgorithm)
+        {
+        }
+#endif
+
+        internal override KeyParameter GetEncoded(
 			string algorithmOid)
 		{
 			Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
diff --git a/crypto/src/crmf/CertificateRequestMessageBuilder.cs b/crypto/src/crmf/CertificateRequestMessageBuilder.cs
index 38e95dfe7..363bfd136 100644
--- a/crypto/src/crmf/CertificateRequestMessageBuilder.cs
+++ b/crypto/src/crmf/CertificateRequestMessageBuilder.cs
@@ -163,8 +163,14 @@ namespace Org.BouncyCastle.Crmf
             return this;
         }
 
+        [Obsolete("Use 'SetAuthInfoPKMacBuilder' instead")]
         public CertificateRequestMessageBuilder SetAuthInfoPKMAC(PKMacBuilder pkmacFactory, char[] password)
         {
+            return SetAuthInfoPKMacBuilder(pkmacFactory, password);
+        }
+
+        public CertificateRequestMessageBuilder SetAuthInfoPKMacBuilder(PKMacBuilder pkmacFactory, char[] password)
+        {
             this._pkMacBuilder = pkmacFactory;
             this._password = password;
 
diff --git a/crypto/src/crmf/PKMacBuilder.cs b/crypto/src/crmf/PKMacBuilder.cs
index 7261a9daf..6db80325d 100644
--- a/crypto/src/crmf/PKMacBuilder.cs
+++ b/crypto/src/crmf/PKMacBuilder.cs
@@ -217,17 +217,31 @@ namespace Org.BouncyCastle.Crmf
         /// <returns>IMacFactory</returns>
         public IMacFactory Build(char[] password)
         {
-            if (parameters != null)
-                return GenCalculator(parameters, password);
-
-            byte[] salt = new byte[saltLength];
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Build(password.AsSpan());
+#else
+            PbmParameter pbmParameter = parameters;
+            if (pbmParameter == null)
+            {
+                pbmParameter = GenParameters();
+            }
 
-            this.random = CryptoServicesRegistrar.GetSecureRandom(random);
+            return GenCalculator(pbmParameter, password);
+#endif
+        }
 
-            random.NextBytes(salt);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public IMacFactory Build(ReadOnlySpan<char> password)
+        {
+            PbmParameter pbmParameter = parameters;
+            if (pbmParameter == null)
+            {
+                pbmParameter = GenParameters();
+            }
 
-            return GenCalculator(new PbmParameter(salt, owf, iterationCount, mac), password);
+            return GenCalculator(pbmParameter, password);
         }
+#endif
 
         private void CheckIterationCountCeiling(int iterationCount)
         {
@@ -235,8 +249,20 @@ namespace Org.BouncyCastle.Crmf
                 throw new ArgumentException("iteration count exceeds limit (" + iterationCount + " > " + maxIterations + ")");
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private IMacFactory GenCalculator(PbmParameter parameters, ReadOnlySpan<char> password)
+        {
+            return GenCalculator(parameters, Strings.ToUtf8ByteArray(password));
+        }
+#else
         private IMacFactory GenCalculator(PbmParameter parameters, char[] password)
         {
+            return GenCalculator(parameters, Strings.ToUtf8ByteArray(password));
+        }
+#endif
+
+        private IMacFactory GenCalculator(PbmParameter parameters, byte[] pw)
+        {
             // From RFC 4211
             //
             //   1.  Generate a random salt value S
@@ -252,7 +278,6 @@ namespace Org.BouncyCastle.Crmf
             //       MAC = HASH( K XOR opad, HASH( K XOR ipad, data) )
             //
             //       Where opad and ipad are defined in [HMAC].
-            byte[] pw = Strings.ToUtf8ByteArray(password);
             byte[] salt = parameters.Salt.GetOctets();
             byte[] K = new byte[pw.Length + salt.Length];
 
@@ -280,5 +305,12 @@ namespace Org.BouncyCastle.Crmf
 
             return new PKMacFactory(key, parameters);
         }
+
+        private PbmParameter GenParameters()
+        {
+            byte[] salt = SecureRandom.GetNextBytes(CryptoServicesRegistrar.GetSecureRandom(random), saltLength);
+
+            return new PbmParameter(salt, owf, iterationCount, mac);
+        }
     }
 }
diff --git a/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs b/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs
index 4cd568e81..4530b18b8 100644
--- a/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs
+++ b/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs
@@ -38,27 +38,22 @@ namespace Org.BouncyCastle.Crmf
         {
             IMacFactory fact = generator.Build(password);
 
-            byte[] d = _pubKeyInfo.GetDerEncoded();
-
-            IStreamCalculator<IBlockResult> calc = fact.CreateCalculator();
-            using (var stream = calc.Stream)
-            {
-                stream.Write(d, 0, d.Length);
-            }
+            return ImplSetPublicKeyMac(fact);
+        }
 
-            this._publicKeyMAC = new PKMacValue(
-                (AlgorithmIdentifier)fact.AlgorithmDetails,
-                new DerBitString(calc.GetResult().Collect()));
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public ProofOfPossessionSigningKeyBuilder SetPublicKeyMac(PKMacBuilder generator, ReadOnlySpan<char> password)
+        {
+            IMacFactory fact = generator.Build(password);
 
-            return this;
+            return ImplSetPublicKeyMac(fact);
         }
+#endif
 
         public PopoSigningKey Build(ISignatureFactory signer)
         {
             if (_name != null && _publicKeyMAC != null)
-            {
                 throw new InvalidOperationException("name and publicKeyMAC cannot both be set.");
-            }
 
             PopoSigningKeyInput popo;
 
@@ -86,5 +81,22 @@ namespace Org.BouncyCastle.Crmf
 
             return new PopoSigningKey(popo, (AlgorithmIdentifier)signer.AlgorithmDetails, new DerBitString(signature));
         }
+
+        private ProofOfPossessionSigningKeyBuilder ImplSetPublicKeyMac(IMacFactory fact)
+        {
+            byte[] d = _pubKeyInfo.GetDerEncoded();
+
+            IStreamCalculator<IBlockResult> calc = fact.CreateCalculator();
+            using (var stream = calc.Stream)
+            {
+                stream.Write(d, 0, d.Length);
+            }
+
+            this._publicKeyMAC = new PKMacValue(
+                (AlgorithmIdentifier)fact.AlgorithmDetails,
+                new DerBitString(calc.GetResult().Collect()));
+
+            return this;
+        }
     }
 }
diff --git a/crypto/src/crypto/PbeParametersGenerator.cs b/crypto/src/crypto/PbeParametersGenerator.cs
index 86927d230..8c3ac30a8 100644
--- a/crypto/src/crypto/PbeParametersGenerator.cs
+++ b/crypto/src/crypto/PbeParametersGenerator.cs
@@ -44,6 +44,15 @@ namespace Org.BouncyCastle.Crypto
             this.mIterationCount = iterationCount;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void Init(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, int iterationCount)
+        {
+            this.mPassword = password.ToArray();
+            this.mSalt = salt.ToArray();
+            this.mIterationCount = iterationCount;
+        }
+#endif
+
         public virtual byte[] Password
         {
             get { return Arrays.Clone(mPassword); }
@@ -105,9 +114,21 @@ namespace Org.BouncyCastle.Crypto
             if (password == null)
                 return new byte[0];
 
-            return Encoding.UTF8.GetBytes(password);
+            return Strings.ToUtf8ByteArray(password);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static byte[] Pkcs5PasswordToBytes(ReadOnlySpan<char> password)
+        {
+            return Strings.ToByteArray(password);
+        }
+
+        public static byte[] Pkcs5PasswordToUtf8Bytes(ReadOnlySpan<char> password)
+        {
+            return Strings.ToUtf8ByteArray(password);
+        }
+#endif
+
         /**
          * converts a password to a byte array according to the scheme in
          * Pkcs12 (unicode, big endian, 2 zero pad bytes at the end).
@@ -137,5 +158,25 @@ namespace Org.BouncyCastle.Crypto
 
             return bytes;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static byte[] Pkcs12PasswordToBytes(ReadOnlySpan<char> password)
+        {
+            return Pkcs12PasswordToBytes(password, false);
+        }
+
+        public static byte[] Pkcs12PasswordToBytes(ReadOnlySpan<char> password, bool wrongPkcs12Zero)
+        {
+            if (password.IsEmpty)
+                return new byte[wrongPkcs12Zero ? 2 : 0];
+
+            // +1 for extra 2 pad bytes.
+            byte[] bytes = new byte[(password.Length + 1) * 2];
+
+            Encoding.BigEndianUnicode.GetBytes(password, bytes);
+
+            return bytes;
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs b/crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs
index 448cd1920..9bdb82317 100644
--- a/crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs
+++ b/crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs
@@ -58,10 +58,23 @@ namespace Org.BouncyCastle.Crypto.Generators
 			base.Init(password, salt, 1);
 		}
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Init(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, int iterationCount)
+        {
+            // Ignore the provided iterationCount
+            base.Init(password, salt, 1);
+        }
+
+        public virtual void Init(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt)
+        {
+            base.Init(password, salt, 1);
+        }
+#endif
+
+        /**
 		 * the derived key function, the ith hash of the password and the salt.
 		 */
-		private byte[] GenerateDerivedKey(
+        private byte[] GenerateDerivedKey(
 			int bytesNeeded)
 		{
 			byte[] buf = new byte[digest.GetDigestSize()];
diff --git a/crypto/src/openssl/MiscPemGenerator.cs b/crypto/src/openssl/MiscPemGenerator.cs
index ada0b84ed..0e918f793 100644
--- a/crypto/src/openssl/MiscPemGenerator.cs
+++ b/crypto/src/openssl/MiscPemGenerator.cs
@@ -128,20 +128,62 @@ namespace Org.BouncyCastle.OpenSsl
             return new PemObject(type, encoding);
         }
 
-//		private string GetHexEncoded(byte[] bytes)
-//		{
-//			bytes = Hex.Encode(bytes);
-//
-//			char[] chars = new char[bytes.Length];
-//
-//			for (int i = 0; i != bytes.Length; i++)
-//			{
-//				chars[i] = (char)bytes[i];
-//			}
-//
-//			return new string(chars);
-//		}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static PemObject CreatePemObject(object obj, string algorithm, ReadOnlySpan<char> password,
+            SecureRandom random)
+        {
+            if (obj == null)
+                throw new ArgumentNullException("obj");
+            if (algorithm == null)
+                throw new ArgumentNullException("algorithm");
+            if (random == null)
+                throw new ArgumentNullException("random");
+
+            if (obj is AsymmetricCipherKeyPair keyPair)
+            {
+                return CreatePemObject(keyPair.Private, algorithm, password, random);
+            }
+
+            string type = null;
+            byte[] keyData = null;
 
+            if (obj is AsymmetricKeyParameter akp)
+            {
+                if (akp.IsPrivate)
+                {
+                    keyData = EncodePrivateKey(akp, out type);
+                }
+            }
+
+            if (type == null || keyData == null)
+            {
+                // TODO Support other types?
+                throw new PemGenerationException("Object type not supported: " + Platform.GetTypeName(obj));
+            }
+
+
+            string dekAlgName = algorithm.ToUpperInvariant();
+
+            // Note: For backward compatibility
+            if (dekAlgName == "DESEDE")
+            {
+                dekAlgName = "DES-EDE3-CBC";
+            }
+
+            int ivLength = Platform.StartsWith(dekAlgName, "AES-") ? 16 : 8;
+
+            byte[] iv = new byte[ivLength];
+            random.NextBytes(iv);
+
+            byte[] encData = PemUtilities.Crypt(true, keyData, password, dekAlgName, iv);
+
+            var headers = new List<PemHeader>(2);
+            headers.Add(new PemHeader("Proc-Type", "4,ENCRYPTED"));
+            headers.Add(new PemHeader("DEK-Info", dekAlgName + "," + Hex.ToHexString(iv).ToUpperInvariant()));
+
+            return new PemObject(type, headers, encData);
+        }
+#else
         private static PemObject CreatePemObject(
             object			obj,
             string			algorithm,
@@ -201,6 +243,7 @@ namespace Org.BouncyCastle.OpenSsl
 
             return new PemObject(type, headers, encData);
         }
+#endif
 
         private static byte[] EncodePrivateKey(
             AsymmetricKeyParameter	akp,
diff --git a/crypto/src/openssl/PEMUtilities.cs b/crypto/src/openssl/PEMUtilities.cs
index 332768083..4ff340b12 100644
--- a/crypto/src/openssl/PEMUtilities.cs
+++ b/crypto/src/openssl/PEMUtilities.cs
@@ -50,6 +50,84 @@ namespace Org.BouncyCastle.OpenSsl
 			throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName);
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static byte[] Crypt(bool encrypt, ReadOnlySpan<byte> bytes, ReadOnlySpan<char> password,
+			string dekAlgName, ReadOnlySpan<byte> iv)
+        {
+            PemBaseAlg baseAlg;
+            PemMode mode;
+            ParseDekAlgName(dekAlgName, out baseAlg, out mode);
+
+            string padding;
+            switch (mode)
+            {
+            case PemMode.CBC:
+            case PemMode.ECB:
+                padding = "PKCS5Padding";
+                break;
+            case PemMode.CFB:
+            case PemMode.OFB:
+                padding = "NoPadding";
+                break;
+            default:
+                throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName);
+            }
+
+            string algorithm;
+
+            ReadOnlySpan<byte> salt = iv;
+            switch (baseAlg)
+            {
+            case PemBaseAlg.AES_128:
+            case PemBaseAlg.AES_192:
+            case PemBaseAlg.AES_256:
+                algorithm = "AES";
+                if (salt.Length > 8)
+                {
+					salt = iv[..8].ToArray();
+                }
+                break;
+            case PemBaseAlg.BF:
+                algorithm = "BLOWFISH";
+                break;
+            case PemBaseAlg.DES:
+                algorithm = "DES";
+                break;
+            case PemBaseAlg.DES_EDE:
+            case PemBaseAlg.DES_EDE3:
+                algorithm = "DESede";
+                break;
+            case PemBaseAlg.RC2:
+            case PemBaseAlg.RC2_40:
+            case PemBaseAlg.RC2_64:
+                algorithm = "RC2";
+                break;
+            default:
+                throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName);
+            }
+
+            string cipherName = algorithm + "/" + mode + "/" + padding;
+            IBufferedCipher cipher = CipherUtilities.GetCipher(cipherName);
+
+            ICipherParameters cParams = GetCipherParameters(password, baseAlg, salt);
+
+            if (mode != PemMode.ECB)
+            {
+                cParams = new ParametersWithIV(cParams, iv);
+            }
+
+            cipher.Init(encrypt, cParams);
+
+			int outputSize = cipher.GetOutputSize(bytes.Length);
+			byte[] output = new byte[outputSize];
+			int length = cipher.DoFinal(bytes, output);
+			if (length < outputSize)
+			{
+				output = Arrays.CopyOfRange(output, 0, length);
+			}
+			return output;
+        }
+#else
 		internal static byte[] Crypt(
 			bool	encrypt,
 			byte[]	bytes,
@@ -124,7 +202,37 @@ namespace Org.BouncyCastle.OpenSsl
 
 			return cipher.DoFinal(bytes);
 		}
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static ICipherParameters GetCipherParameters(ReadOnlySpan<char> password, PemBaseAlg baseAlg,
+            ReadOnlySpan<byte> salt)
+        {
+            string algorithm;
+            int keyBits;
+            switch (baseAlg)
+            {
+            case PemBaseAlg.AES_128: keyBits = 128; algorithm = "AES128"; break;
+            case PemBaseAlg.AES_192: keyBits = 192; algorithm = "AES192"; break;
+            case PemBaseAlg.AES_256: keyBits = 256; algorithm = "AES256"; break;
+            case PemBaseAlg.BF: keyBits = 128; algorithm = "BLOWFISH"; break;
+            case PemBaseAlg.DES: keyBits = 64; algorithm = "DES"; break;
+            case PemBaseAlg.DES_EDE: keyBits = 128; algorithm = "DESEDE"; break;
+            case PemBaseAlg.DES_EDE3: keyBits = 192; algorithm = "DESEDE3"; break;
+            case PemBaseAlg.RC2: keyBits = 128; algorithm = "RC2"; break;
+            case PemBaseAlg.RC2_40: keyBits = 40; algorithm = "RC2"; break;
+            case PemBaseAlg.RC2_64: keyBits = 64; algorithm = "RC2"; break;
+            default:
+                return null;
+            }
+
+            OpenSslPbeParametersGenerator pGen = new OpenSslPbeParametersGenerator();
+
+            pGen.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password), salt);
 
+            return pGen.GenerateDerivedParameters(algorithm, keyBits);
+        }
+#else
 		private static ICipherParameters GetCipherParameters(
 			char[]		password,
 			PemBaseAlg	baseAlg,
@@ -154,5 +262,6 @@ namespace Org.BouncyCastle.OpenSsl
 
 			return pGen.GenerateDerivedParameters(algorithm, keyBits);
 		}
-	}
+#endif
+    }
 }
diff --git a/crypto/src/security/JksStore.cs b/crypto/src/security/JksStore.cs
index c5ef92a70..4df0b39db 100644
--- a/crypto/src/security/JksStore.cs
+++ b/crypto/src/security/JksStore.cs
@@ -50,11 +50,15 @@ namespace Org.BouncyCastle.Security
         /// <exception cref="IOException"/>
         public AsymmetricKeyParameter GetKey(string alias, char[] password)
         {
-            if (alias == null)
-                throw new ArgumentNullException(nameof(alias));
             if (password == null)
                 throw new ArgumentNullException(nameof(password));
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GetKey(alias, password.AsSpan());
+#else
+            if (alias == null)
+                throw new ArgumentNullException(nameof(alias));
+
             if (!m_keyEntries.TryGetValue(alias, out JksKeyEntry keyEntry))
                 return null;
 
@@ -84,15 +88,89 @@ namespace Org.BouncyCastle.Security
                 throw new IOException("cannot recover key");
 
             return PrivateKeyFactory.CreateKey(pkcs8Key);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        public AsymmetricKeyParameter GetKey(string alias, ReadOnlySpan<char> password)
+        {
+            if (alias == null)
+                throw new ArgumentNullException(nameof(alias));
+
+            if (!m_keyEntries.TryGetValue(alias, out JksKeyEntry keyEntry))
+                return null;
+
+            if (!JksObfuscationAlg.Equals(keyEntry.keyData.EncryptionAlgorithm))
+                throw new IOException("unknown encryption algorithm");
+
+            byte[] encryptedData = keyEntry.keyData.GetEncryptedData();
+
+            // key length is encryptedData - salt - checksum
+            int pkcs8Len = encryptedData.Length - 40;
+
+            IDigest digest = DigestUtilities.GetDigest("SHA-1");
+
+            // key decryption
+            byte[] keyStream = CalculateKeyStream(digest, password, encryptedData, pkcs8Len);
+            byte[] pkcs8Key = new byte[pkcs8Len];
+            for (int i = 0; i < pkcs8Len; ++i)
+            {
+                pkcs8Key[i] = (byte)(encryptedData[20 + i] ^ keyStream[i]);
+            }
+            Array.Clear(keyStream, 0, keyStream.Length);
+
+            // integrity check
+            byte[] checksum = GetKeyChecksum(digest, password, pkcs8Key);
+
+            if (!Arrays.FixedTimeEquals(20, encryptedData, pkcs8Len + 20, checksum, 0))
+                throw new IOException("cannot recover key");
+
+            return PrivateKeyFactory.CreateKey(pkcs8Key);
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private byte[] GetKeyChecksum(IDigest digest, ReadOnlySpan<char> password, ReadOnlySpan<byte> pkcs8Key)
+        {
+            AddPassword(digest, password);
+
+            return DigestUtilities.DoFinal(digest, pkcs8Key);
+        }
+#else
         private byte[] GetKeyChecksum(IDigest digest, char[] password, byte[] pkcs8Key)
         {
             AddPassword(digest, password);
 
             return DigestUtilities.DoFinal(digest, pkcs8Key);
         }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private byte[] CalculateKeyStream(IDigest digest, ReadOnlySpan<char> password, ReadOnlySpan<byte> salt,
+            int count)
+        {
+            byte[] keyStream = new byte[count];
 
+            Span<byte> hash = stackalloc byte[20];
+            hash.CopyFrom(salt);
+
+            int index = 0;
+            while (index < count)
+            {
+                AddPassword(digest, password);
+
+                digest.BlockUpdate(hash);
+                digest.DoFinal(hash);
+
+                int length = System.Math.Min(hash.Length, keyStream.Length - index);
+                keyStream.AsSpan(index, length).CopyFrom(hash);
+                index += length;
+            }
+
+            return keyStream;
+        }
+#else
         private byte[] CalculateKeyStream(IDigest digest, char[] password, byte[] salt, int count)
         {
             byte[] keyStream = new byte[count];
@@ -113,6 +191,7 @@ namespace Org.BouncyCastle.Security
 
             return keyStream;
         }
+#endif
 
         public X509Certificate[] GetCertificateChain(string alias)
         {
@@ -128,7 +207,10 @@ namespace Org.BouncyCastle.Security
                 return certEntry.cert;
 
             if (m_keyEntries.TryGetValue(alias, out var keyEntry))
-                return keyEntry.chain?[0];
+            {
+                var chain = keyEntry.chain;
+                return chain == null || chain.Length == 0 ? null : chain[0];
+            }
 
             return null;
         }
@@ -147,6 +229,52 @@ namespace Org.BouncyCastle.Security
         /// <exception cref="IOException"/>
         public void SetKeyEntry(string alias, AsymmetricKeyParameter key, char[] password, X509Certificate[] chain)
         {
+            if (password == null)
+                throw new ArgumentNullException(nameof(password));
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            SetKeyEntry(alias, key, password.AsSpan(), chain);
+#else
+            alias = ConvertAlias(alias);
+
+            if (ContainsAlias(alias))
+                throw new IOException("alias [" + alias + "] already in use");
+
+            byte[] pkcs8Key = PrivateKeyInfoFactory.CreatePrivateKeyInfo(key).GetEncoded();
+            byte[] protectedKey = new byte[pkcs8Key.Length + 40];
+
+            SecureRandom rnd = CryptoServicesRegistrar.GetSecureRandom();
+            rnd.NextBytes(protectedKey, 0, 20);
+
+            IDigest digest = DigestUtilities.GetDigest("SHA-1");
+
+            byte[] checksum = GetKeyChecksum(digest, password, pkcs8Key);
+            Array.Copy(checksum, 0, protectedKey, 20 + pkcs8Key.Length, 20);
+
+            byte[] keyStream = CalculateKeyStream(digest, password, protectedKey, pkcs8Key.Length);
+            for (int i = 0; i != keyStream.Length; i++)
+            {
+                protectedKey[20 + i] = (byte)(pkcs8Key[i] ^ keyStream[i]);
+            }
+            Array.Clear(keyStream, 0, keyStream.Length);
+
+            try
+            {
+                var epki = new EncryptedPrivateKeyInfo(JksObfuscationAlg, protectedKey);
+                m_keyEntries.Add(alias, new JksKeyEntry(DateTime.UtcNow, epki.GetEncoded(), CloneChain(chain)));
+            }
+            catch (Exception e)
+            {
+                throw new IOException("unable to encode encrypted private key", e);
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        public void SetKeyEntry(string alias, AsymmetricKeyParameter key, ReadOnlySpan<char> password,
+            X509Certificate[] chain)
+        {
             alias = ConvertAlias(alias);
 
             if (ContainsAlias(alias))
@@ -180,6 +308,7 @@ namespace Org.BouncyCastle.Security
                 throw new IOException("unable to encode encrypted private key", e);
             }
         }
+#endif
 
         /// <exception cref="IOException"/>
         public void SetKeyEntry(string alias, byte[] key, X509Certificate[] chain)
@@ -254,12 +383,36 @@ namespace Org.BouncyCastle.Security
         /// <exception cref="IOException"/>
         public void Save(Stream stream, char[] password)
         {
-            if (stream == null)
-                throw new ArgumentNullException(nameof(stream));
             if (password == null)
                 throw new ArgumentNullException(nameof(password));
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Save(stream, password.AsSpan());
+#else
+            if (stream == null)
+                throw new ArgumentNullException(nameof(stream));
+
             IDigest checksumDigest = CreateChecksumDigest(password);
+
+            SaveStream(stream, checksumDigest);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        public void Save(Stream stream, ReadOnlySpan<char> password)
+        {
+            if (stream == null)
+                throw new ArgumentNullException(nameof(stream));
+
+            IDigest checksumDigest = CreateChecksumDigest(password);
+
+            SaveStream(stream, checksumDigest);
+        }
+#endif
+
+        private void SaveStream(Stream stream, IDigest checksumDigest)
+        {
             BinaryWriter bw = new BinaryWriter(new DigestStream(stream, null, checksumDigest));
 
             BinaryWriters.WriteInt32BigEndian(bw, Magic);
@@ -286,7 +439,7 @@ namespace Org.BouncyCastle.Security
                 }
             }
 
-            foreach (var entry in m_certificateEntries) 
+            foreach (var entry in m_certificateEntries)
             {
                 string alias = entry.Key;
                 JksTrustedCertEntry certEntry = entry.Value;
@@ -302,74 +455,101 @@ namespace Org.BouncyCastle.Security
             bw.Flush();
         }
 
+        /// <remarks>WARNING: If <paramref name="password"/> is <c>null</c>, no integrity check is performed.</remarks>
         /// <exception cref="IOException"/>
         public void Load(Stream stream, char[] password)
         {
             if (stream == null)
                 throw new ArgumentNullException(nameof(stream));
 
-            m_certificateEntries.Clear();
-            m_keyEntries.Clear();
+            using (var storeStream = ValidateStream(stream, password))
+            {
+                LoadStream(storeStream);
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        public void Load(Stream stream, ReadOnlySpan<char> password)
+        {
+            if (stream == null)
+                throw new ArgumentNullException(nameof(stream));
 
             using (var storeStream = ValidateStream(stream, password))
             {
-                BinaryReader br = new BinaryReader(storeStream);
+                LoadStream(storeStream);
+            }
+        }
+#endif
 
-                int magic = BinaryReaders.ReadInt32BigEndian(br);
-                int storeVersion = BinaryReaders.ReadInt32BigEndian(br);
+        /// <summary>Load without any integrity check.</summary>
+        /// <exception cref="IOException"/>
+        public void LoadUnchecked(Stream stream)
+        {
+            Load(stream, null);
+        }
+
+        private void LoadStream(ErasableByteStream storeStream)
+        {
+            m_certificateEntries.Clear();
+            m_keyEntries.Clear();
 
-                if (!(magic == Magic && (storeVersion == 1 || storeVersion == 2)))
-                    throw new IOException("Invalid keystore format");
+            BinaryReader br = new BinaryReader(storeStream);
 
-                int numEntries = BinaryReaders.ReadInt32BigEndian(br);
+            int magic = BinaryReaders.ReadInt32BigEndian(br);
+            int storeVersion = BinaryReaders.ReadInt32BigEndian(br);
 
-                for (int t = 0; t < numEntries; t++)
-                {
-                    int tag = BinaryReaders.ReadInt32BigEndian(br);
+            if (!(magic == Magic && (storeVersion == 1 || storeVersion == 2)))
+                throw new IOException("Invalid keystore format");
 
-                    switch (tag)
-                    {
-                    case 1: // keys
-                    {
-                        string alias = ReadUtf(br);
-                        DateTime date = ReadDateTime(br);
+            int numEntries = BinaryReaders.ReadInt32BigEndian(br);
+
+            for (int t = 0; t < numEntries; t++)
+            {
+                int tag = BinaryReaders.ReadInt32BigEndian(br);
 
-                        // encrypted key data
-                        byte[] keyData = ReadBufferWithInt32Length(br);
+                switch (tag)
+                {
+                case 1: // keys
+                {
+                    string alias = ReadUtf(br);
+                    DateTime date = ReadDateTime(br);
 
-                        // certificate chain
-                        int chainLength = BinaryReaders.ReadInt32BigEndian(br);
-                        X509Certificate[] chain = null;
-                        if (chainLength > 0)
+                    // encrypted key data
+                    byte[] keyData = ReadBufferWithInt32Length(br);
+
+                    // certificate chain
+                    int chainLength = BinaryReaders.ReadInt32BigEndian(br);
+                    X509Certificate[] chain = null;
+                    if (chainLength > 0)
+                    {
+                        var certs = new List<X509Certificate>(System.Math.Min(10, chainLength));
+                        for (int certNo = 0; certNo != chainLength; certNo++)
                         {
-                            var certs = new List<X509Certificate>(System.Math.Min(10, chainLength));
-                            for (int certNo = 0; certNo != chainLength; certNo++)
-                            {
-                                certs.Add(ReadTypedCertificate(br, storeVersion));
-                            }
-                            chain = certs.ToArray();
+                            certs.Add(ReadTypedCertificate(br, storeVersion));
                         }
-                        m_keyEntries.Add(alias, new JksKeyEntry(date, keyData, chain));
-                        break;
+                        chain = certs.ToArray();
                     }
-                    case 2: // certificate
-                    {
-                        string alias = ReadUtf(br);
-                        DateTime date = ReadDateTime(br);
+                    m_keyEntries.Add(alias, new JksKeyEntry(date, keyData, chain));
+                    break;
+                }
+                case 2: // certificate
+                {
+                    string alias = ReadUtf(br);
+                    DateTime date = ReadDateTime(br);
 
-                        X509Certificate cert = ReadTypedCertificate(br, storeVersion);
+                    X509Certificate cert = ReadTypedCertificate(br, storeVersion);
 
-                        m_certificateEntries.Add(alias, new JksTrustedCertEntry(date, cert));
-                        break;
-                    }
-                    default:
-                        throw new IOException("unable to discern entry type");
-                    }
+                    m_certificateEntries.Add(alias, new JksTrustedCertEntry(date, cert));
+                    break;
+                }
+                default:
+                    throw new IOException("unable to discern entry type");
                 }
-
-                if (storeStream.Position != storeStream.Length)
-                    throw new IOException("password incorrect or store tampered with");
             }
+
+            if (storeStream.Position != storeStream.Length)
+                throw new IOException("password incorrect or store tampered with");
         }
 
         /*
@@ -391,7 +571,11 @@ namespace Org.BouncyCastle.Security
 
             if (password != null)
             {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                byte[] checksum = CalculateChecksum(password, rawStore.AsSpan(0, checksumPos));
+#else
                 byte[] checksum = CalculateChecksum(password, rawStore, 0, checksumPos);
+#endif
 
                 if (!Arrays.FixedTimeEquals(20, checksum, 0, rawStore, checksumPos))
                 {
@@ -403,6 +587,36 @@ namespace Org.BouncyCastle.Security
             return new ErasableByteStream(rawStore, 0, checksumPos);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        private ErasableByteStream ValidateStream(Stream inputStream, ReadOnlySpan<char> password)
+        {
+            byte[] rawStore = Streams.ReadAll(inputStream);
+            int checksumPos = rawStore.Length - 20;
+
+            byte[] checksum = CalculateChecksum(password, rawStore.AsSpan(0, checksumPos));
+
+            if (!Arrays.FixedTimeEquals(20, checksum, 0, rawStore, checksumPos))
+            {
+                Array.Clear(rawStore, 0, rawStore.Length);
+                throw new IOException("password incorrect or store tampered with");
+            }
+
+            return new ErasableByteStream(rawStore, 0, checksumPos);
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void AddPassword(IDigest digest, ReadOnlySpan<char> password)
+        {
+            // Encoding.BigEndianUnicode
+            for (int i = 0; i < password.Length; ++i)
+            {
+                digest.Update((byte)(password[i] >> 8));
+                digest.Update((byte)password[i]);
+            }
+        }
+#else
         private static void AddPassword(IDigest digest, char[] password)
         {
             // Encoding.BigEndianUnicode
@@ -412,13 +626,23 @@ namespace Org.BouncyCastle.Security
                 digest.Update((byte)password[i]);
             }
         }
+#endif
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static byte[] CalculateChecksum(ReadOnlySpan<char> password, ReadOnlySpan<byte> buffer)
+        {
+            IDigest checksumDigest = CreateChecksumDigest(password);
+            checksumDigest.BlockUpdate(buffer);
+            return DigestUtilities.DoFinal(checksumDigest);
+        }
+#else
         private static byte[] CalculateChecksum(char[] password, byte[] buffer, int offset, int length)
         {
             IDigest checksumDigest = CreateChecksumDigest(password);
             checksumDigest.BlockUpdate(buffer, offset, length);
             return DigestUtilities.DoFinal(checksumDigest);
         }
+#endif
 
         private static X509Certificate[] CloneChain(X509Certificate[] chain)
         {
@@ -430,6 +654,22 @@ namespace Org.BouncyCastle.Security
             return alias.ToLowerInvariant();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static IDigest CreateChecksumDigest(ReadOnlySpan<char> password)
+        {
+            IDigest digest = DigestUtilities.GetDigest("SHA-1");
+            AddPassword(digest, password);
+
+            //
+            // This "Mighty Aphrodite" string goes all the way back to the
+            // first java betas in the mid 90's, why who knows? But see
+            // https://cryptosense.com/mighty-aphrodite-dark-secrets-of-the-java-keystore/
+            //
+            byte[] prefix = Encoding.UTF8.GetBytes("Mighty Aphrodite");
+            digest.BlockUpdate(prefix);
+            return digest;
+        }
+#else
         private static IDigest CreateChecksumDigest(char[] password)
         {
             IDigest digest = DigestUtilities.GetDigest("SHA-1");
@@ -444,6 +684,7 @@ namespace Org.BouncyCastle.Security
             digest.BlockUpdate(prefix, 0, prefix.Length);
             return digest;
         }
+#endif
 
         private static byte[] ReadBufferWithInt16Length(BinaryReader br)
         {
diff --git a/crypto/src/util/Strings.cs b/crypto/src/util/Strings.cs
index 12eafd21e..29a95a07e 100644
--- a/crypto/src/util/Strings.cs
+++ b/crypto/src/util/Strings.cs
@@ -46,6 +46,18 @@ namespace Org.BouncyCastle.Utilities
             return bs;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static byte[] ToByteArray(ReadOnlySpan<char> cs)
+        {
+            byte[] bs = new byte[cs.Length];
+            for (int i = 0; i < bs.Length; ++i)
+            {
+                bs[i] = Convert.ToByte(cs[i]);
+            }
+            return bs;
+        }
+#endif
+
         public static string FromAsciiByteArray(byte[] bytes)
         {
             return Encoding.ASCII.GetString(bytes);
@@ -75,5 +87,15 @@ namespace Org.BouncyCastle.Utilities
         {
             return Encoding.UTF8.GetBytes(s);
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static byte[] ToUtf8ByteArray(ReadOnlySpan<char> cs)
+        {
+            int count = Encoding.UTF8.GetByteCount(cs);
+            byte[] bytes = new byte[count];
+            Encoding.UTF8.GetBytes(cs, bytes);
+            return bytes;
+        }
+#endif
     }
 }