using System; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.OpenSsl { internal sealed class PemUtilities { private enum PemBaseAlg { AES_128, AES_192, AES_256, BF, DES, DES_EDE, DES_EDE3, RC2, RC2_40, RC2_64 }; private enum PemMode { CBC, CFB, ECB, OFB }; static PemUtilities() { // Signal to obfuscation tools not to change enum constants Enums.GetArbitraryValue().ToString(); Enums.GetArbitraryValue().ToString(); } private static void ParseDekAlgName(string dekAlgName, out PemBaseAlg baseAlg, out PemMode mode) { if (dekAlgName == "DES-EDE" || dekAlgName == "DES-EDE3") { if (Enums.TryGetEnumValue(dekAlgName, out baseAlg)) { mode = PemMode.ECB; return; } } else { int pos = dekAlgName.LastIndexOf('-'); if (pos >= 0) { if (Enums.TryGetEnumValue(dekAlgName.Substring(0, pos), out baseAlg) && Enums.TryGetEnumValue(dekAlgName.Substring(pos + 1), out mode)) { return; } } } throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName); } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER internal static byte[] Crypt(bool encrypt, ReadOnlySpan bytes, ReadOnlySpan password, string dekAlgName, ReadOnlySpan 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 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, char[] password, string dekAlgName, 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; byte[] salt = iv; switch (baseAlg) { case PemBaseAlg.AES_128: case PemBaseAlg.AES_192: case PemBaseAlg.AES_256: algorithm = "AES"; if (salt.Length > 8) { salt = new byte[8]; Array.Copy(iv, 0, salt, 0, salt.Length); } 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); return cipher.DoFinal(bytes); } #endif #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER private static ICipherParameters GetCipherParameters(ReadOnlySpan password, PemBaseAlg baseAlg, ReadOnlySpan 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, 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); } #endif } }