How to decrypt using rsa from PEM file

2019-02-25 23:34发布

问题:

I am using the following c# code to encrypt and decrypt using rsa with PEM files:

public string encrypt(string elementToEncrypt, string pathPrivateKey) {
        string pem = System.IO.File.ReadAllText(pathPrivateKey);
        byte[] Buffer = getBytesFromPEMFile(pem, "PUBLIC KEY");
        System.Security.Cryptography.RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
        System.Security.Cryptography.RSAParameters rsaParam = rsa.ExportParameters(false);
        rsaParam.Modulus = Buffer;
        rsa.ImportParameters(rsaParam);
        byte[] encryptedMessageByte = rsa.Encrypt(Convert.FromBase64String(elementToEncrypt),false);
        return Convert.ToBase64String(encryptedMessageByte);
    }

public string decrypt(string elementToDesencrypt, string pathPublicKey)
    {
        string pem = System.IO.File.ReadAllText(pathPublicKey);
        byte[] Buffer = getBytesFromPEMFile(pem, "RSA PRIVATE KEY");
        System.Security.Cryptography.RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
        System.Security.Cryptography.RSAParameters rsaParam = rsa.ExportParameters(false);
        rsaParam.Modulus = Buffer;
        rsa.ImportParameters(rsaParam);
        byte[] encryptedMessageByte = rsa.Decrypt(Convert.FromBase64String(elementToDesencrypt), false);
        return Convert.ToBase64String(encryptedMessageByte);
    }

public byte[] getBytesFromPEMFile(string pemString, string headerPEM) {
        string header = String.Format("-----BEGIN {0}-----", headerPEM);
        string footer = String.Format("-----END {0}-----", headerPEM);
        int start = pemString.IndexOf(header, StringComparison.Ordinal) + header.Length;
        int end = pemString.IndexOf(footer, start, StringComparison.Ordinal) - start;
        if (start < 0 || end < 0)
        {
            return null;
        }
        return Convert.FromBase64String(pemString.Substring(start, end));
    }

But the problem is when i want to decrypt in the line:

byte[] encryptedMessageByte = rsa.Decrypt(Convert.FromBase64String(elementToDesencrypt), false);

and the error that i am getting is that the key does not exist.

My pem files are:

key.pem

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDLPKI8p+ANRabCTdLvJjuT0wx1kt2voJ0+BtdTRBqhJQbRgM2P
dtHilmaVSyiVtD5l1mTl+h8mFRBttiH0VgW3KuyvFk2mrjF78MrsXlYoHVizGgeh
UWVUsNh7EhdgF/hM7miZMXsHoa/MEQwgytPwjpDbOXXECZz8CpHiyNOftwIDAQAB
AoGAUrmXgAEFHeHgAu8SkO2LCpy5UZI6UiaaWokGVIpAHJ+pqtU21tKSlByMHPC+
0FDRpTojT8kDrMieK0obgA0TvcUaARVPGZsLjB4WZLKh7e8LPaUTvAS9dTmKd7xB
4YGFKY+AJb38VdDU9CoQMsiPtIIiPWz09lgGvYRGzXmTBwECQQDsEtLRyOijXISK
iFhtdpBI4yAmnTYyYLrsPXgS7asa80h7vnTmOlUpuqsxZtWNVGcpNiYG4y8OpJU5
Jr8IkNnXAkEA3GRC63+SEbEo5wXcrHF+tzxfFmk3yzS38w5jtGik3yrp6psyjaQ8
Q+D3RaKjGYtjTH3pmljRH2OGEvrNwvFtIQJAFkLgJnAvn9gFl5qr3AamLHleesWw
aqe8eLKDNCW9UNlIKIMZOuydQ0YbBpmP4bfn0ncMtvGNanASskT5FrGyGQJASE7k
3dsnE4LqhpGXy0QZbQjzsain05XiXG52K/TBUy8DPCPbPDmMREEFH+WyWWkwFSKi
iC9nvUKr9IIxDCqlwQJBAIDwEg6yVGdVCQry+OEGtsiaGPveX+lAx/kULba0wfRq
KaQAstQrT7p+ONtC8x8NHDE/ayjz6GlEZ7svR/LZO7w=
-----END RSA PRIVATE KEY-----

and pubkey.pem

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLPKI8p+ANRabCTdLvJjuT0wx1
kt2voJ0+BtdTRBqhJQbRgM2PdtHilmaVSyiVtD5l1mTl+h8mFRBttiH0VgW3Kuyv
Fk2mrjF78MrsXlYoHVizGgehUWVUsNh7EhdgF/hM7miZMXsHoa/MEQwgytPwjpDb
OXXECZz8CpHiyNOftwIDAQAB
-----END PUBLIC KEY-----

I have read that if I want to decrypt, I should have both keys into one pem file, but if I do it, I would not know what to put on

rsaParam.Modulus = Buffer;

I mean, do I have to mix both buffers (private and public) into one? if so, how should I do that?.

Thanks for the help.

回答1:

.NET does not have built-in support for reading "bare key" files.

If you paste the Base64 component of your public key file to https://lapo.it/asn1js/ you'll see that it can be decomposed to

SEQUENCE
  SEQUENCE
    OBJECT IDENTIFIER rsaEncryption
    NULL
  BIT STRING
    SEQUENCE
      INTEGER (1024-bit) ...
      INTEGER 65537

To import it into .NET you need to copy the bytes (not the decimal value) of the first integer (1024-bit in this example) into the RSAParameters.Modulus value, and the bytes of the second integer into the RSAParameters.Exponent value. In both cases if there's a leading 00 byte you need to leave that off.

For your current key:

RSAParameters rsaParams = new RSAParameters
{
    Modulus = YourFavoriteHexParser(
        "CB3CA23CA7E00D45A6C24DD2EF263B93D30C7592DDAFA09D3E06D753441AA125" +
        "06D180CD8F76D1E29666954B2895B43E65D664E5FA1F2615106DB621F45605B7" +
        "2AECAF164DA6AE317BF0CAEC5E56281D58B31A07A1516554B0D87B12176017F8" +
        "4CEE6899317B07A1AFCC110C20CAD3F08E90DB3975C4099CFC0A91E2C8D39FB7"),
    Exponent = new byte[] { 0x01, 0x00, 0x01 },
};

RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParams);
return rsa;

More generically, you'd need to parse the public key file as an ASN.1 DER blob, then consume the Modulus and Exponent values out of the payload.

The easiest workaround if you need to do it more than once is to use OpenSSL to make a self-signed certificate for the key file, since .NET can use keys from certificates (cert.GetRSAPublicKey()).

Adding support for bare keys is on the .NET Core roadmap (https://github.com/dotnet/corefx/issues/20414), and one can reasonably well assume that after it gets added to .NET Core it will make it to .NET Framework.

For decrypting you need the private key. Again, the easiest answer is to make a self-signed certificate from the key, bundle the cert and key together into a PFX/PKCS#12 file, and use cert.GetRSAPrivateKey(). But, for the hard way:

Pasting the private key blob we see that it looks like

SEQUENCE
  INTEGER 0
  INTEGER (1024-bit)
  INTEGER 65537
  INTEGER (1023-bit)
  INTEGER (512-bit)
  INTEGER (512-bit)
  INTEGER (509-bit)
  INTEGER (511-bit)
  INTEGER (512-bit)

In https://tools.ietf.org/html/rfc8017#appendix-A.1.2 we see that the RSAPrivateKey structure looks like

RSAPrivateKey ::= SEQUENCE {
    version           Version,
    modulus           INTEGER,  -- n
    publicExponent    INTEGER,  -- e
    privateExponent   INTEGER,  -- d
    prime1            INTEGER,  -- p
    prime2            INTEGER,  -- q
    exponent1         INTEGER,  -- d mod (p-1)
    exponent2         INTEGER,  -- d mod (q-1)
    coefficient       INTEGER,  -- (inverse of q) mod p
    otherPrimeInfos   OtherPrimeInfos OPTIONAL

And that version == 0 means that there are only two primes. Now we run into some quirks. In .NET, the D value must have the same size as Modulus. If D happened to come out to a "1016-bit" number (or smaller) you'd need to insert leading 0x00 values. .NET also requires that P be exactly half the size of Modulus (round up if it matters), and that Q, DP, DQ, and InverseQ all have the same size as P.

So, again, for your key:

RSAParameters rsaParams = new RSAParameters
{
    Modulus = YourFavoriteHexParser(
        "CB3CA23CA7E00D45A6C24DD2EF263B93D30C7592DDAFA09D3E06D753441AA125" +
        "06D180CD8F76D1E29666954B2895B43E65D664E5FA1F2615106DB621F45605B7" +
        "2AECAF164DA6AE317BF0CAEC5E56281D58B31A07A1516554B0D87B12176017F8" +
        "4CEE6899317B07A1AFCC110C20CAD3F08E90DB3975C4099CFC0A91E2C8D39FB7"),
    Exponent = new byte[] { 0x01, 0x00, 0x01 },
    D = YourFavoriteHexParser(
        "52B9978001051DE1E002EF1290ED8B0A9CB951923A52269A5A8906548A401C9F" +
        "A9AAD536D6D292941C8C1CF0BED050D1A53A234FC903ACC89E2B4A1B800D13BD" +
        "C51A01154F199B0B8C1E1664B2A1EDEF0B3DA513BC04BD75398A77BC41E18185" +
        "298F8025BDFC55D0D4F42A1032C88FB482223D6CF4F65806BD8446CD79930701"),
    P = YourFavoriteHexParser(
        "EC12D2D1C8E8A35C848A88586D769048E320269D363260BAEC3D7812EDAB1AF3" +
        "487BBE74E63A5529BAAB3166D58D546729362606E32F0EA4953926BF0890D9D7"),
    Q = YourFavoriteHexParser(
        "DC6442EB7F9211B128E705DCAC717EB73C5F166937CB34B7F30E63B468A4DF2A" +
        "E9EA9B328DA43C43E0F745A2A3198B634C7DE99A58D11F638612FACDC2F16D21"),
    DP = YourFavoriteHexParser(
        "1642E026702F9FD805979AABDC06A62C795E7AC5B06AA7BC78B2833425BD50D9" +
        "482883193AEC9D43461B06998FE1B7E7D2770CB6F18D6A7012B244F916B1B219"),
    DQ = YourFavoriteHexParser(
        "484EE4DDDB271382EA869197CB44196D08F3B1A8A7D395E25C6E762BF4C1532F" +
        "033C23DB3C398C4441051FE5B25969301522A2882F67BD42ABF482310C2AA5C1"),
    InverseQ = YourFavoriteHexParser(
        "80F0120EB2546755090AF2F8E106B6C89A18FBDE5FE940C7F9142DB6B4C1F46A" +
        "29A400B2D42B4FBA7E38DB42F31F0D1C313F6B28F3E8694467BB2F47F2D93BBC"),
};

RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParams);
return rsa;


回答2:

I've created a library for reading and writing PEM / ASN.1 encoded files. See https://github.com/huysentruitw/pem-utils

Can be installed from NuGet:

PM> Install-Package PemUtils

Usage

using (var stream = File.OpenRead(path))
using (var reader = new PemReader(stream))
{
    var rsaParameters = reader.ReadRsaKey();
    rsa.ImportParameters(rsaParameters);
}