5

我有一个生成 BouncyCastle RSA 密钥对的函数。我需要加密私钥,然后将加密的私钥和公钥存储到单独的 SQL2008 数据库字段中。

我正在使用以下内容获取密钥对:

private static AsymmetricCipherKeyPair createASymRandomCipher()
{
    RsaKeyPairGenerator r = new RsaKeyPairGenerator();
    r.Init(new KeyGenerationParameters(new SecureRandom(), 1024));
    AsymmetricCipherKeyPair keys = r.GenerateKeyPair();
    return keys; 
}

这可以很好地返回密钥,但我不确定如何加密私钥并随后将其存储在数据库中。

这就是我目前使用的加密数据(不正确?):

public static byte[] encBytes2(AsymmetricKeyParameter keyParam, byte[] Key, byte[] IV)
{
    MemoryStream ms = new MemoryStream();
    Rijndael rjdAlg = Rijndael.Create();
    rjdAlg.Key = Key;
    rjdAlg.IV = IV;
    CryptoStream cs = new CryptoStream(ms, rjdAlg.CreateEncryptor(), CryptoStreamMode.Write);
    byte[] keyBytes = System.Text.Encoding.Unicode.GetBytes(keyParam.ToString());
    cs.Write(keyBytes, 0, keyBytes.Length);
    cs.Close();
    byte[] encryptedData = ms.ToArray();
    return encryptedData;
}

显然,我在其中转换 keyParam.ToString() 的 keyBytes 设置不正确,因为它只转换 KeyParameter 名称,而不是实际值。我正在向此函数提交keys.Private 的先前密钥对返回。

另一个问题是因为我没有加密公钥,我应该以什么格式将它存储在 SQL2008 数据库中,nvarchar(256) 或其他?

任何帮助将不胜感激。

4

4 回答 4

17

出于应该清楚的原因,默认(可能是无意的)序列化不能很好地与私钥一起使用,私钥只能在非常有限的情况下写出。

BouncyCastle 支持 PKCS#8,这是“序列化”私钥的相关标准。有称为 PrivateKeyInfo 和 EncryptedPrivateKeyInfo 的 ASN.1 结构。因为它们在 ASN.1 中,所以有标准的方法来序列化/反序列化它们。顾名思义,一个以明文形式存储密钥,另一个根据密码对密钥进行加密。

对于公钥 - 这些通常不会被加密。BC 支持 SubjectPublicKeyInfo 的 X.509 标准格式用于序列化它们。

在 C# 构建中,要查看的高级类是:

  • Org.BouncyCastle.Security.PrivateKeyFactory
  • org.BouncyCastle.Security.PublicKeyFactory
  • Org.BouncyCastle.Pkcs.EncryptedPrivateKeyInfoFactory
  • Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory
  • Org.BouncyCastle.X509.SubjectPublicKeyInfoFactory
于 2010-02-02T15:42:40.937 回答
11

只要对象被标记为可序列化,将对象转换为字节数组的一种方法是使用 .Net 中的 BinaryFormatter 类。

您需要将此 using 语句添加到您的代码文件中:

using System.Runtime.Serialization.Formatters.Binary;

二进制格式化程序可以将您的类输出到流中。当您打算将对象转换为字节数组时,您可以使用 System.IO.MemoryStream 作为临时存储。

MemoryStream memStream = new MemoryStream();

然后,您可以创建一个新的二进制格式化程序。

BinaryFormatter formatter = new BinarryFomatter();

并使用它来序列化您的对象。

formatter.Serialize(memStream, someObject);

要获取字节,您可以使用:

return memStream.ToArray();

要反序列化字节数组,您需要将字节写入内存流。

memStream.Write(arrBytes, 0, arrBytes.Length);

返回到流的开头。

memStream.Seek(0, SeekOrigin.Begin);

然后使用格式化程序重新创建对象。

Object obj = (Object)formatter.Deserialize(memStream);

如果您已经在使用加密函数,您应该能够在将创建的字节数组存储到数据库之前很容易地对其进行加密。

希望这将帮助您朝着正确的方向前进。如果幸运的话,BouncyCastle 对象将被标记为可序列化,否则您将需要一些额外的代码。稍后,我将有机会查看 BouncyCastle 库以便能够对此进行测试,并在必要时发布更多代码。


...我以前从未使用过 BouncyCastle。经过一些测试,似乎公钥和私钥对象是不可序列化的,因此您需要将这些对象转换为可序列化的对象!

似乎公钥和私钥将属性公开为各种 BouncyCastle.Math.BigInteger 值。(也可以从这些 BigInteger 构造键)。此外,BigIntegers 有一个 ToByteArray() 函数,也可以从字节数组构造。很有用..

知道您可以将每个键分解为 BigIntegers,然后将它们分解为字节数组,并且反过来也是可能的,您可以将所有这些都存储在可序列化的对象中。一个简单的结构或类就可以了,例如

[Serializable]
private struct CipherPrivateKey
{
    public byte[] modulus;
    public byte[] publicExponent;
    public byte[] privateExponent;
    public byte[] p;
    public byte[] q;
    public byte[] dP;
    public byte[] dQ;
    public byte[] qInv;
}

[Serializable]
private struct CipherPublicKey
{
    public bool isPrivate;
    public byte[] modulus;
    public byte[] exponent;
}

这为我们提供了一对易于使用的可序列化对象。

AsymmetricCipherKeyPair 将公钥和私钥公开为 AsymmetricKeyParameter 对象。要获得更详细的属性,您需要将它们转换为以下内容:

keyPair.Public 到 BouncyCastle.Crypto.Parameters.RsaKeyParameters keyPair.Private 到 BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters

以下函数会将这些转换为之前声明的结构:

private static CipherPublicKey getCipherPublicKey(Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters cPublic)
{
    CipherPublicKey cpub = new CipherPublicKey();
    cpub.modulus = cPublic.Modulus.ToByteArray();
    cpub.exponent = cPublic.Exponent.ToByteArray();
    return cpub;
}
private static CipherPrivateKey getCipherPrivateKey(Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters cPrivate)
{
    CipherPrivateKey cpri = new CipherPrivateKey();
    cpri.dP = cPrivate.DP.ToByteArray();
    cpri.dQ = cPrivate.DQ.ToByteArray();
    cpri.modulus = cPrivate.Modulus.ToByteArray();
    cpri.p = cPrivate.P.ToByteArray();
    cpri.privateExponent = cPrivate.Exponent.ToByteArray();
    cpri.publicExponent = cPrivate.PublicExponent.ToByteArray();
    cpri.q = cPrivate.Q.ToByteArray();
    cpri.qInv = cPrivate.QInv.ToByteArray();
    return cpri;
}

使用前面提到的二进制格式化程序,我们可以将刚刚创建的可序列化对象转换为字节数组。

CipherPublicKey cpub = getCipherPublicKey((Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)keypair.Public);
MemoryStream memStream = new MemoryStream();
BinaryFormatter formatter = new BinarryFomatter();
formatter.Serialize(memStream, cpub);
return memStream.ToArray();

反序列化就是前面描述的反序列化。反序列化公共或私有结构后,您可以使用 BouncyCastle 构造函数重新创建密钥。这些函数证明了这一点。

private static Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters recreateASymCipherPublicKey(CipherPublicKey cPublicKey)
{
    Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters key;
    key = new Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters(
            cPublicKey.isPrivate,
            createBigInteger(cPublicKey.modulus),
            createBigInteger(cPublicKey.exponent));
    return key;
}

private static Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters recreateASymCipherPrivateKey(CipherPrivateKey cPrivateKey)
{
    Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters key;
    key = new Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters(
            createBigInteger(cPrivateKey.modulus),
            createBigInteger(cPrivateKey.publicExponent),
            createBigInteger(cPrivateKey.privateExponent),
            createBigInteger(cPrivateKey.p),
            createBigInteger(cPrivateKey.q),
            createBigInteger(cPrivateKey.dP),
            createBigInteger(cPrivateKey.dQ),
            createBigInteger(cPrivateKey.qInv));
    return key;
}

如果您出于任何原因需要重新创建原始密钥对:

AsymmetricKeyParameter publ = (AsymmetricKeyParameter)recreateASymCipherPublicKey(cKeyPair.publicKey);
AsymmetricKeyParameter priv = (AsymmetricKeyParameter)recreateASymCipherPrivateKey(cKeyPair.privateKey);
AsymmetricCipherKeyPair keyPair = new AsymmetricCipherKeyPair(publ, priv);

希望这一切都说得通!代码示例应该可以帮助您。

于 2009-05-15T15:03:46.723 回答
6

正确的做法是使用彼得斯的建议。

我在下面包含了一个小的 C# 代码示例:

var keyPair = GetKeypair();

PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);                        
byte[] serializedKey = privateKeyInfo.ToAsn1Object().GetDerEncoded();

AsymmetricKeyParameter deserializedKey1 = PrivateKeyFactory.CreateKey(serializedKey);
Assert.AreEqual(keyPair.Private, deserializedKey1);

AsymmetricKeyParameter deserializedKey2 = PrivateKeyFactory.CreateKey(privateKeyInfo);            
Assert.AreEqual(keyPair.Private, deserializedKey2);

该示例使用 Bouncy Castle API。请注意,示例不加密密钥。该CreatePrivateKeyInfo方法被重载以允许使用密码作为密钥的保护。

于 2010-09-28T10:57:42.180 回答
1

关于问题的第二部分,应该用于存储密钥的数据类型是 VARBINARY(256)。

回到问题的第一部分,您实际上可以选择让 SQL Server 为您处理加密。当然,您是否愿意这样做取决于您的应用程序要求是什么,但如果可以选择,我会仔细考虑。

我们将在这里非常基础,只使用对称密钥和三重 DES。

首先,数据库有一个主密钥,用于保护证书和非对称密钥。主密钥使用 Triple-DES 加密。

CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'supersecretpassword'  

SQL Server 2005/2008 可以生成自己的 X.509 证书,用于保护用于加密实际数据的密钥。

CREATE CERTIFICATE ExampleCertificate 
     WITH SUBJECT = 'thisisjustsomemetadata'

有很多加密对称密钥(证书、密码、其他密钥)的选项,以及许多支持的算法。但是对于这个例子,我们将使用我们的证书。

CREATE SYMMETRIC KEY ExampleKey
     WITH ALGORITHM = TRIPLE_DES  
     ENCRYPTION BY CERTIFICATE EncryptTestCert 

密钥需要使用与加密相同的方法进行解密。在我们的例子中,这将是我们创建的证书。

 DECLARE @Value VARCHAR(50)
 SET @Value = 'supersecretdata!'

 OPEN SYMMETRIC KEY ExampleKey DECRYPTION BY CERTIFICATE ExampleCertificate  
     UPDATE SomeTable  
     SET SomeColumn = ENCRYPTBYKEY(KEY_GUID('ExampleKey'), @Value)

解密同样简单。

OPEN SYMMETRIC KEY ExampleKey DECRYPTION BY CERTIFICATE ExampleCertificate  
     SELECT CONVERT(VARCHAR(50),DECRYPTBYKEY(SomeColumn)) AS DecryptedData 
     FROM SomeTable 

希望这可以解决您的问题,或者至少为您打开了替代解决方案的大门(尽管有在 C# 应用程序中进行加密经验的人可能会在您的上述代码中找到错误)。如果您的要求要求数据甚至不能以纯文本形式通过网络传输到 SQL Server,那么显然这是不行的(好吧,您实际上可以创建到 SQL Server 的 SSL 连接......)。

于 2009-05-11T04:33:23.420 回答