.NET 內建的 RSA 加解密相關元件可以用讀取憑證檔或是 XML 格式的金鑰的方式來初始化, 之前的專案都是用讀取 XML 的方式來操作, 讓管理者能夠方便的從後台來管理金鑰.
而在 PEM 檔中, 金鑰的格式是 base64 字串, 這用 JAVA 是可以正常讀取的, 但卻不是 .NET 接受的 XML 格式, 因此用了一個第三方套件
Bouncy Castle
來幫忙轉換, 但是在一個少見的情境下 (其實也沒有多少見, 我用 OpenSSL 隨機生 500 組, 就有 10 組觸發), 轉換出來的 XML 是錯誤的, 且只有私鑰有發生過.
官方JIRA 上的單子 BMA-92
可以看出是在
Org.BouncyCastle.Math.BigInteger
初始化過程造成的, 從原始碼中看起來也很合理(不過我只是
看看原始碼推測的
, 沒有直接拉下來驗證, 不保證真的是):
從 public BigInteger(byte[] bytes) : this(bytes, 0, bytes.Length)
=> public BigInteger(byte[] bytes, int offset, int length)
=> private static int[] MakeMagnitude(byte[] bytes, int offset, int length)
中的 firstSignificant 這個 flag 以第一個非 0 位置為起點
而用這個發現來推論, 也可以理解為什麼公鑰不會有這問題, 因為公鑰只有 Modulus 和 Exponent 這兩段.
原始碼
看可以發現, 他在要轉換成 XML 字串前, 將開頭有 0 被忽略的部分再補回來, 實測後也確實可以避免原本的問題, 他的 call stack 有點長, 這裡直接列出最關鍵的地方如下, 就是
ConvertRSAParametersField
這個方法才避免掉這個問題的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public static RSAParameters ToRSAParameters(RsaPrivateKeyStructure privKey) { RSAParameters rp = new RSAParameters(); rp.Modulus = privKey.Modulus.ToByteArrayUnsigned(); rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned(); rp.P = privKey.Prime1.ToByteArrayUnsigned(); rp.Q = privKey.Prime2.ToByteArrayUnsigned(); rp.D = ConvertRSAParametersField(privKey.PrivateExponent, rp.Modulus.Length); rp.DP = ConvertRSAParametersField(privKey.Exponent1, rp.P.Length); rp.DQ = ConvertRSAParametersField(privKey.Exponent2, rp.Q.Length); rp.InverseQ = ConvertRSAParametersField(privKey.Coefficient, rp.Q.Length); return rp; }
private static byte[] ConvertRSAParametersField(BigInteger n, int size) { byte[] bs = n.ToByteArrayUnsigned();
if (bs.Length == size) return bs;
if (bs.Length > size) throw new ArgumentException("Specified size too small", "size");
byte[] padded = new byte[size]; Array.Copy(bs, 0, padded, size - bs.Length, bs.Length); return padded; }
|
JIRA 的列表
就可以看到這個問題是 1.8.0 才修復的, 但是 GitHub 的時候已經是 1.8.0 版了, 所以如果真的想比對舊版的原始碼的話, 就要去
官網
考古了.
RSA Key Formats - Key File Encoding
C#和JAVA的RSA密钥、公钥转换
JIRA of Bouncy Castle