Consider this test:
- I open a cmd prompt.
dotnet new console --name Keys
- Replace the contents of Program.cs with the following code snippet:
using System.Security.Cryptography;
const string originalKey = "MIICXQIBAAKBgQC9/9FGsQFqJim5XaNp12yHjySy3RO9q0ZWPl97bmep4XR/Tx3bDfTPzIcp/NWrRQP0XSbSwBTFq2ypYWXLtWg5CCMtrwzkK0iSy5KrKA1XwsatAW/AfmCtbyUK0BUfs/D54vXdB0WS48TK Ab/sIcvupag4O4e9NFB y/dlr4MVwIDAQABAoGASsSm2EjDo8AM31NIAViy7s2XxYNWR2dlMH8vF WkiaedLpQ1zYQ6eKOl9RH4C4QHQFx/8KOCCR ijS003 stbcZtPrigAFTLU3bmbBbSfkQjvgMCgMiiqIITlnFFPWI5OqBn5PcKqun6EJvvUxdrAxXQEuXFH9j34O6t0wVJ CECQQDjAPy1sCAyDkSYRtcolAhbE8TcUGk8atchILdyE/sc0JbssdcZDfvMqdRC155W8V3iPgm3iVZs2WDGDum24mCJAkEA1kTIeJIt9X 1MZcKPi7ArZSCyiwbsDo874mDEIrk3l8h43rcyVq61qdlRGcdyLMT2NSzxVsebsIUez2kiD0N3wJAUalRP6sUae1oD7 sNxTJzLnX38mtkeZ9bZVvaMJ3W25OXOe9EW5OXtnZWhJnC6/YrkLTDAuD47Rvc9B5kyjswQJBALWHUpwrpDpANt9LikcCTwUANApach7MSEHcK6kBM0NeL5TMy27fqjkfWsEn52jYprDmC2PhfZfyX23F3LX7m9sCQQC1uu65Dwl/UopM34Km7NRm N1TC26UiaWxcYXgYafE22Dy2XGhUMpolIAMoz9wkw2HW4QihtZ6Jwq6VXbOdQu4";
// Import the key
using var rsa = RSA.Create();
rsa.ImportRSAPrivateKey(Convert.FromBase64String(originalKey), out _);
// Export the key
var exported = Convert.ToBase64String(rsa.ExportRSAPrivateKey());
if (exported == originalKey)
Console.WriteLine("Pass");
else
Console.WriteLine($"Failed: exported key is \"{exported}\".");
dotnet run
I have run this test on three different machines, which I'll call Alice, Bob, and Carrie.
Alice has dotnet --version
6.0.400, Windows 10, and Visual Studio 2022. On Alice, this test prints "Pass".
Bob has dotnet --version
6.0.400, Windows 10, and Visual Studio 2019. On Bob, it prints "Pass".
Carrie has dotnet --version
6.0.400, Windows Server 2016 (Version 1607), and Visual Studio 2019. On Carrie, it prints "Failed" with this exported key:
MIICXQIBAAKBgQC9/9FGsQFqJim5XaNp12yHjySy3RO9q0ZWPl97bmep4XR/Tx3bDfTPzIcp/NWrRQP0XSbSwBTFq2ypYWXLtWg5CCMtrwzkK0iSy5KrKA1XwsatAW/AfmCtbyUK0BUfs/D54vXdB0WS48TK Ab/sIcvupag4O4e9NFB y/dlr4MVwIDAQABAoGAC29hFg3DKwipoYlm3hDkFvM2NI76XYOjE7 57sDXUQchBCSBLyo M1945xMGJ8JbRD1y/7jQceZ VLdoRq61W1bOG MHI6jidcuqKNZkTrDERuSxbO1kIA0/ 6zIfXn4z5ok10AWYX8o4CEB5zx0w8CkHG8XPHs7R1tiDegVGNECQQDjAPy1sCAyDkSYRtcolAhbE8TcUGk8atchILdyE/sc0JbssdcZDfvMqdRC155W8V3iPgm3iVZs2WDGDum24mCJAkEA1kTIeJIt9X 1MZcKPi7ArZSCyiwbsDo874mDEIrk3l8h43rcyVq61qdlRGcdyLMT2NSzxVsebsIUez2kiD0N3wJAUalRP6sUae1oD7 sNxTJzLnX38mtkeZ9bZVvaMJ3W25OXOe9EW5OXtnZWhJnC6/YrkLTDAuD47Rvc9B5kyjswQJBALWHUpwrpDpANt9LikcCTwUANApach7MSEHcK6kBM0NeL5TMy27fqjkfWsEn52jYprDmC2PhfZfyX23F3LX7m9sCQQC1uu65Dwl/UopM34Km7NRm N1TC26UiaWxcYXgYafE22Dy2XGhUMpolIAMoz9wkw2HW4QihtZ6Jwq6VXbOdQu4
If I reimport this into an RSA key, all the individual RSAParameters
fields are the same on both machines except the D
parameter, which is completely different.
On all three machines, RSA.Create().GetType().Assembly.Location
is the same, and the assembly at the given location is the same (or has the same checksum at least).
If I do this with a new randomly generated key, it usually passes on all three machines. There must be something uncooperative about this particular key. But I would have expected an RSA key to be a task that is independent of the machine doing the task, particularly in a high-level runtime like dotnet 6.
Yes, I could just replace the key to a new one in order to make it pass, but I'm more concerned with finding out: What could be going on here to cause this to fail on one machine but pass on others? (particularly given that the .net sdk is the same version on both)
CodePudding user response:
The original key used Euler's totient function when calculating the private exponent (d = 0x4AC4A6...), while the modified key used Carmichael's totient function when calculating the private exponent (d = 0x0B6F61...).
Nowadays Carmichael's totient function is mostly used. Apparently, the first two machines do not change the key, while the last machine somehow differs and forces a conversion that conforms to the algorithm that uses the Carmichael's totient function.
Both private keys have the same public key and are equivalent in that either private key can be used to decrypt a message encrypted with their (identical) public key.
This follows from the fact that any d satisfying d⋅e ≡ 1 (mod φ(n)) also satisfies d⋅e ≡ 1 (mod λ(n)), where φ(n) is Euler's totient function and λ(n) is Carmichael's totient function. I.e. the private exponents of the original and modified key satisfy d⋅e ≡ 1 (mod λ(n)). For details s. here.
The d determined with Euler's totient function can be larger than λ(n). This is e.g. the case for the original key. Since some standards/FIPS require d < λ(n), this can lead to problems. A too large d can be reduced modulo λ(n) to d < λ(n) (giving the d determined with the Carmichael's totient function).
Here the underlying values:
n = 0xbdffd146b1016a2629b95da369d76c878f24b2dd13bdab46563e5f7b6e67a9e1747f4f1ddb0df4cfcc8729fcd5ab4503f45d26d2c014c5ab6ca96165cbb5683908232daf0ce42b4892cb92ab280d57c2c6ad016fc07e60ad6f250ad0151fb3f0f9e2f5dd074592e3c4caf806ffb0872fba96a0e0ee1ef4d141fb2fdd96be0c57
e = 0x010001
p = 0xe300fcb5b020320e449846d72894085b13c4dc50693c6ad72120b77213fb1cd096ecb1d7190dfbcca9d442d79e56f15de23e09b789566cd960c60ee9b6e26089
q = 0xd644c878922df57fb531970a3e2ec0ad9482ca2c1bb03a3cef8983108ae4de5f21e37adcc95abad6a76544671dc8b313d8d4b3c55b1e6ec2147b3da4883d0ddf
φ(n) = (p − 1)(q − 1) = 0xbdffd146b1016a2629b95da369d76c878f24b2dd13bdab46563e5f7b6e67a9e1747f4f1ddb0df4cfcc8729fcd5ab4503f45d26d2c014c5ab6ca96165cbb568374edd6880ca9603ba9901b4c9c14a8eba1e655af33b91bb995e7ad04d763fb8c14112c92924dcdc40739170c84390e2bdff83e36409aa1935ccb9e34f579e9df0
d_eul = e^−1 (mod φ(n)) = 0x4ac4a6d848c3a3c00cdf53480158b2eecd97c58356476765307f2f17e5a489a79d2e9435cd843a78a3a5f511f80b8407405c7ff0a382091fa28d2d34dfeb2d6dc66d3eb8a00054cb5376e66c16d27e4423be030280c8a2a882139671453d62393aa067e4f70aaae9fa109bef53176b0315d012e5c51fd8f7e0eeadd30549f821
λ(n) = lcm(p − 1, q − 1) = 0x1faaa2e11d803c5bb19ee4f091a3e76bed30c87a2df4f1e10e5fba9492669c503e1537da4f2cfe22a21686ff78f1e0d5fe0f86787558cb9c921c3ae64c9e3c0937cf916acc6e55f46ed59e21a03717c9afbb8f2889ed9f443a69cd623e5ff42035832186db7a24b568983d76b5ed7b1faa95fb3b56f1aede4cc9a5e28e9a6fa8
d_car = e^−1 (mod λ(n)) = 0x0b6f61160dc32b08a9a18966de10e416f336348efa5d83a313bfb9eec0d75107210424812f2a3e335f78e7130627c25b443d72ffb8d071e67e54b76846aeb55b56ce1be30723a8e275cbaa28d6644eb0c446e4b16ced64200d3ffbacc87d79f8cf9a24d74016617f28e02101e73c74c3c0a41c6f173c7b3b475b620de81518d1
d_eul⋅e = 1 (mod λ(n))
d_eul > λ(n): d_eul (mod λ(n)) = d_car
φ(n) = 6⋅λ(n)
Edit:
In this post found by the OP it is additionally explained how the combination of provider, .NET version and OS version can have different effects regarding the generation as well as import/export of d.
Depending on the combination, d is retained or recalculated for export, and Euler's totient function or Carmichael's totient function is used for the calculation.