.NET RSAParameters endianness

Ever had to access or set the RSAParameters structure’s fields? Well, if you do, you will find that the MSDN documentation on them doesn’t specify their endianness (byte order). Googling isn’t too helpful either, as I found different sources claiming different endianness. So, I wrote a little test in F# that shows that the RSAParameters fields are indeed big-endian.

The test consists of a few functions for PKCS#1 padding and conversions from byte arrays (RSAParameters fields are byte arrays) to bigints and back, and a function for the RSA encryption/decryption formula, me (mod n). The conversions to and from bigint assumes that RSAParameter fields are big-endian, and MSDN documentation tells us that byte arrays to and from bigint are little-endian.

If we can encrypt a message with my home brewed encryption function, and decrypt it back to the original message with .NET’s RSACryptoServiceProvider.Decrypt, then the two crypto functions agree, and we know that the assumption about big-endianness is correct. Now for the code:

open System
open System.Security.Cryptography
open System.Text

// A more readable way to concatenate arrays.
let inline private (++) (a:^T[]) (b:^T[]) = Array.append a b

// Create an unsigned bigint from a big endian byte array
// (appending leading null makes the bigint unsigned).
let unsignedBigInt bytes = bigint ( [| 0uy |] ++ bytes |> Array.rev )

// Get a big endian byte array from a bigint.
let unsignedBigIntToByteArray (n : bigint) =
    let a = n.ToByteArray() |> Array.rev
    match a.[0] with
    | 0x00uy -> a.[1..]  // strip leading 0 (which marks positive int)
    | _      -> a

// PKCS#1 v1.5 padding. Prefer PKCS#1 v2 for production use.
let pad (msg : byte[]) keyModulusLen =
    // The pad string should be random if done properly.
    let padString = Array.create (keyModulusLen - msg.Length - 3) 0xFFuy
    [| 0x00uy; 0x02uy |] ++ padString ++ [| 0x00uy|] ++ msg

let rsaEncrypt data exponent modulus modulusLen =
    let m = unsignedBigInt (pad data modulusLen)
    Numerics.BigInteger.ModPow(m, exponent, modulus)
    |> unsignedBigIntToByteArray

let testRSAParams () =
    // Generate a key pair.
    let rsaSP = new RSACryptoServiceProvider()
    // Get public and private keys.
    let rsaParams = rsaSP.ExportParameters(true)

    let originalMsg = "Lorem ipsum dolor sit amet."
    let originalMsgBytes = Encoding.Unicode.GetBytes(originalMsg)

    // Encrypt with home brewed RSA,
    // assuming that rsaParams keys are big endian, unlike bigint.
    let bigintExp = unsignedBigInt rsaParams.Exponent
    let bigintMod = unsignedBigInt rsaParams.Modulus
    let encr = rsaEncrypt originalMsgBytes bigintExp bigintMod rsaParams.Modulus.Length

    // Decrypt our home brew encrypted data with RSAProvider and compare the results.
    let decr = rsaSP.Decrypt(encr, false)
               |> Encoding.Unicode.GetString

    if decr = originalMsg then
        "Bingo! Assumptions about big-endianness were right."
    else
        "Back to the drawing board, assumptions were wrong."

Of course, this test tells us that RSAParameters fields are big-endian. Since bigint is little-endian, I wrongly thought that the same was true for RSAParameters, and my first version of the above test failed accordingly. Ironically, after writing the test I found this article that could have saved me some work. Still, the learning experience is (almost) always worth it!

References:

  1. No comments yet.

  1. No trackbacks yet.