{"id":2837,"date":"2025-11-18T16:11:57","date_gmt":"2025-11-18T16:11:57","guid":{"rendered":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/11\/18\/post-quantum-cryptography-in-net\/"},"modified":"2025-11-18T16:11:57","modified_gmt":"2025-11-18T16:11:57","slug":"post-quantum-cryptography-in-net","status":"publish","type":"post","link":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/11\/18\/post-quantum-cryptography-in-net\/","title":{"rendered":"Post-Quantum Cryptography in .NET"},"content":{"rendered":"<p>The main focus of .NET Cryptography for .NET 10 was adding support for Post-Quantum Cryptography (PQC).<br \/>\nCryptography work tends to be in the \u201cit\u2019s important, but not really worth talking about\u201d camp,<br \/>\nbut since there\u2019s been a fair amount of buzz this year regarding PQC,<br \/>\nthis seems like a good time to talk about PQC in .NET.<\/p>\n<p>First, a note about nomenclature.<br \/>\nThe \u201cPost\u201d in \u201cPost-Quantum\u201d doesn\u2019t mean \u201cquantum computers are here,\u201d<br \/>\nit mostly means \u201calgorithms that won\u2019t be compromised by the existence of a sufficiently powerful quantum computer.\u201d<br \/>\nEven that is overreaching, because a \u201ccryptographically-relevant quantum computer\u201d (CRQC) won\u2019t have as significant an impact<br \/>\non AES, the SHA-2 family of hash algorithms, or the SHA-3 family of hash algorithms as it will for ECC (EC-DSA, EC-Diffie-Hellman, etc) or RSA.<br \/>\nSo, mainly, \u201cPQC\u201d just means \u201csome new algorithms we\u2019re adding because quantum computers are a threat to RSA and ECC.\u201d<\/p>\n<p>Strategies like \u201c<a href=\"https:\/\/en.wikipedia.org\/wiki\/Harvest_now,_decrypt_later\">Harvest now, decrypt later<\/a>\u201d mean that the transition<br \/>\nfrom \u201ctraditional\u201d asymmetric cryptography to PQC should be done <em>before<\/em> CRQCs exist.<br \/>\nDepending on the futurist you ask (and how important your data is),<br \/>\nthe time to switch is anywhere from \u201cyears ago\u201d to \u201cmaybe never\u201d.<br \/>\nWe don\u2019t have a time machine, so we can\u2019t solve it for \u201cyears ago\u201d,<br \/>\nbut we achieved \u201cin our first release after the first specifications were standardized\u201d,<br \/>\nso \u201cnow\u201d is about as good as it gets!<\/p>\n<p>In .NET 10 we\u2019re focusing on 4 PQC algorithms:<\/p>\n<table>\n<thead>\n<tr>\n<th>Algorithm<\/th>\n<th>Kind<\/th>\n<th>Specification<\/th>\n<th>.NET Class<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>ML-KEM<\/td>\n<td>Key Encapsulation<\/td>\n<td>NIST <a href=\"https:\/\/nvlpubs.nist.gov\/nistpubs\/FIPS\/NIST.FIPS.203.pdf\">FIPS 203<\/a><\/td>\n<td><a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.security.cryptography.mlkem\">MLKem<\/a><\/td>\n<\/tr>\n<tr>\n<td>ML-DSA<\/td>\n<td>Signature<\/td>\n<td>NIST <a href=\"https:\/\/nvlpubs.nist.gov\/nistpubs\/FIPS\/NIST.FIPS.204.pdf\">FIPS 204<\/a><\/td>\n<td><a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.security.cryptography.mldsa\">MLDsa<\/a><\/td>\n<\/tr>\n<tr>\n<td>SLH-DSA<\/td>\n<td>Signature<\/td>\n<td>NIST <a href=\"https:\/\/nvlpubs.nist.gov\/nistpubs\/FIPS\/NIST.FIPS.205.pdf\">FIPS 205<\/a><\/td>\n<td><a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.security.cryptography.slhdsa\">SlhDsa<\/a><\/td>\n<\/tr>\n<tr>\n<td>Composite ML-DSA<\/td>\n<td>Signature<\/td>\n<td>IETF Draft \u201c<a href=\"https:\/\/datatracker.ietf.org\/doc\/draft-ietf-lamps-pq-composite-sigs\/\">Composite ML-DSA for use in X.509 Public Key Infrastructure<\/a>\u201c<\/td>\n<td><a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.security.cryptography.compositemldsa\">CompositeMLDsa<\/a><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>ML-DSA, SLH-DSA, and Composite ML-DSA are all replacements for signatures by RSA and EC-DSA.<br \/>\nML-KEM logically replaces both RSA \u201cKey Transport\u201d and EC-Diffie-Hellman \u201cKey Agreement\u201d, though the actual usage isn\u2019t close to being a drop-in replacement for either of them.<br \/>\nThere is no direct replacement for RSA \u201cData Encryption\u201d, but that\u2019s because that\u2019s not a recommended use of RSA in the first place.<\/p>\n<h2>The Way We\u2019ve Always Done It<\/h2>\n<p>Generally speaking, when you\u2019re doing \u201ca thing\u201d that\u2019s \u201clike some other thing\u201d, you should do it in the same way.<br \/>\nIn .NET Cryptography, we have an established pattern for keys of asymmetric algorithms:<\/p>\n<ul>\n<li>Algorithm types derive from <code>AsymmetricAlgorithm<\/code><\/li>\n<li>Implementation types derive from the algorithm types<\/li>\n<li>Algorithm types have a static <code>Create()<\/code> method that works no matter what OS you\u2019re on (unless the algorithm just isn\u2019t supported on your OS).<\/li>\n<li>Keys can then be imported, explicitly generated, or implicitly generated.<\/li>\n<\/ul>\n<pre><code class=\"language-csharp\">namespace System.Security.Cryptography;\n\npublic partial class AsymmetricAlgorithm : IDisposable { }\npublic partial class RSA : AsymmetricAlgorithm\n{\n    public static RSA Create();\n}\npublic partial class DSA : AsymmetricAlgorithm\n{\n    public static DSA Create();\n}\npublic partial class RSACng : RSA { }\npublic partial class RSAOpenSsl : RSA { }\n\/\/ etc<\/code><\/pre>\n<h2>It Starts To Go Wrong<\/h2>\n<p>When we started the PQC project, the obvious answer was that we should continue to extend <code>AsymmetricAlgorithm<\/code>,<br \/>\nbut there was sort of a hint that was a bad answer\u2026 and that\u2019s the <code>KeySize<\/code> property on <code>AsymmetricAlgorithm<\/code>:<\/p>\n<pre><code class=\"language-csharp\">public partial class AsymmetricAlgorithm\n{\n    public virtual int KeySize { get; set; }\n}<\/code><\/pre>\n<p>This property was introduced when .NET only supported RSA and DSA, and it mostly made sense:<br \/>\nwhen creating an RSA key or a DSA key, pretty much the only parameter is the RSA modulus (n) size or the DSA prime modulus (p) size.<br \/>\nRSA\u2019s <code>KeySize<\/code> values and DSA\u2019s <code>KeySize<\/code> values shouldn\u2019t be compared across the algorithms, but they are a property of any given key.<\/p>\n<p>Then we introduced EC-DSA and EC-DiffieHellman.<br \/>\nECC keys have a simple integer value as a private key, a number in the range <code>[1, p)<\/code>, where <code>p<\/code> is the prime modulus for the \u201ccurve\u201d.<br \/>\nSo, OK, everyone agrees that the \u201ckey size\u201d of an ECC key is \u201cthe number of bits required to represent <code>p<\/code>\u201c.<br \/>\n.NET at the time only supported 3 curves: NIST P-256, NIST P-384, and NIST P-521.<br \/>\nThe number after \u201cP-\u201d is \u201chow many bits are required to represent <code>p<\/code>\u201c,<br \/>\nso now we have a sensible answer for this property:<br \/>\nthe getter reports the number of bits required to represent <code>p<\/code>,<br \/>\nand the setter chooses from NIST P-256, NIST P-384, and NIST P-521.<\/p>\n<p>Then Windows added support for more elliptic curves, so .NET added support for more elliptic curves.<br \/>\nBrainpool\u2019s <code>brainpool384r1<\/code> and NIST\u2019s P-384 both report <code>384<\/code> from the getter, but what should the setter do?<br \/>\nThe best answer we came up with was \u201cthe setter still picks from the 3 options it had before, and we need a new way to inspect or specify the curve.\u201d<\/p>\n<p>That was basically like hearing a creak of wood on a calm day while standing next to a dam.<\/p>\n<p>So now we\u2019re adding more new algorithms.<br \/>\nGiven an ML-DSA-65 key, what should we report as the <code>KeySize<\/code> value?<br \/>\n\u201c65\u201d is an obvious answer, but it\u2019s sort of meaningless (that name just means that this \u201cparameter set\u201d makes use of a 6\u00d75 matrix).<br \/>\nThe \u201craw\u201d public key for ML-DSA-65 is 1952 bytes, so maybe 1952?<br \/>\nWell, this is cryptography, so it should be in bits: 15616?<\/p>\n<p>This was the start of a journey where we decided to \u201cbreak up\u201d with <code>AsymmetricAlgorithm<\/code>.<\/p>\n<h2>\u2026 Anything Else?<\/h2>\n<p>Many moons ago I saw a poster that said something like \u201cChange is terrible\u2026 unless it\u2019s great!\u201d.<br \/>\nBased on where it was, I think the target audience was UX designers and the poster was saying<br \/>\n\u201cusers hate it when you move buttons around, so if you\u2019re going to move it, you better have a great reason\u201d.<br \/>\nRegardless of the intended audience, the message has resonated with me all these years,<br \/>\nand so I knew that we needed something other than \u201cAsymmetricAlgorithm, but without the KeySize property.\u201d<br \/>\nSo, we took a look at what things we like about <code>AsymmetricAlgorithm<\/code>, and what parts we don\u2019t.<\/p>\n<p>The bad parts:<\/p>\n<ul>\n<li>Heavy use of <code>public virtual<\/code> means that we have to repeat state and argument validation in every derived type.\n<ul>\n<li>And sometimes we didn\u2019t repeat it correctly.<\/li>\n<\/ul>\n<\/li>\n<li>You have to create an instance to ask about its capabilities (e.g. <code>public virtual LegalKeySizes[] { get; }<\/code>)<\/li>\n<li><code>Create()<\/code> doesn\u2019t generate a key, in case you do import.  As a result, key generation happens when the key is first needed, making for some perf surprises.<\/li>\n<li><code>Dispose()<\/code> doesn\u2019t always mean \u201cthe object is unusable\u201d, often it meant \u201cI\u2019ve abandoned this key, but I can generate another one!\u201d<\/li>\n<li>You can\u2019t really use it as-is. If you accept one you need to cast it to an algorithm type.<\/li>\n<li><code>KeySize<\/code> doesn\u2019t seem to make sense for these new algorithms.<\/li>\n<li><code>KeyExchangeAlgorithm<\/code>, <code>SignatureAlgorithm<\/code>, <code>ToXmlString(bool)<\/code>, <code>FromXmlString(string)<\/code> are intrusions from <code>SignedXml<\/code> and <code>EncryptedXml<\/code>, they\u2019re at the wrong layer.<\/li>\n<li><code>ExportParameters(bool)<\/code> makes it hard to write a consistent flow analyzer for when you have private key data or public key data.<\/li>\n<\/ul>\n<p>The good parts:<\/p>\n<ul>\n<li>There\u2019s a consistent way to import\/export keys.<\/li>\n<\/ul>\n<p>Clearly, once we started making the \u201cbreakup\u201d list, it was pretty obvious.<\/p>\n<p>Sorry, <code>AsymmetricAlgorithm<\/code>, it\u2019s not you, it\u2019s me (P.S.: it\u2019s totally you).<\/p>\n<h2>The New Design\u2019s Goals<\/h2>\n<ol>\n<li>Instances represent a key\/keypair.<\/li>\n<li>Once disposed, always disposed.<\/li>\n<li>Don\u2019t have a \u201ccommon base class\u201d when two things don\u2019t really have anything in common.<\/li>\n<li>Minimize code for derived types, so that we can minimize the room for mistakes.<\/li>\n<li>Use existing terminology when it means the same thing.<\/li>\n<li>Use new terminology when the existing terminology means something else.<\/li>\n<li>Design for Span<\/li>\n<\/ol>\n<h2>The New Design<\/h2>\n<p>Here\u2019s a view of the class for ML-DSA, with most of the overloads removed for brevity:<\/p>\n<pre><code class=\"language-csharp\">namespace System.Security.Cryptography;\n\npublic abstract partial class MLDsa : System.IDisposable\n{\n    public static bool IsSupported { get; }\n\n    protected MLDsa(MLDsaAlgorithm algorithm);\n    public MLDsaAlgorithm Algorithm { get; }\n\n    public void Dispose();\n    protected virtual void Dispose(bool disposing);\n\n    \/\/ Generate a new key\n    public static MLDsa GenerateKey(MLDsaAlgorithm algorithm);\n\n    \/\/ Algorithm-specific key format imports\n    public static MLDsa ImportMLDsaPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan&lt;byte&gt; source);\n    public static MLDsa ImportMLDsaPrivateKey(MLDsaAlgorithm algorithm, ReadOnlySpan&lt;byte&gt; source);\n    public static MLDsa ImportMLDsaPrivateSeed(MLDsaAlgorithm algorithm, ReadOnlySpan&lt;byte&gt; source);\n\n    \/\/ Standard key container format imports\n    public static MLDsa ImportSubjectPublicKeyInfo(ReadOnlySpan&lt;byte&gt; source);\n    public static MLDsa ImportPkcs8PrivateKey(ReadOnlySpan&lt;byte&gt; source);\n    public static MLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan&lt;char&gt; password, ReadOnlySpan&lt;byte&gt; source);\n    public static MLDsa ImportFromPem(ReadOnlySpan&lt;char&gt; source);\n    public static MLDsa ImportFromEncryptedPem(ReadOnlySpan&lt;char&gt; source, ReadOnlySpan&lt;char&gt; password);\n\n    \/\/ Algorithm-specific key format exports\n    public void ExportMLDsaPublicKey(Span&lt;byte&gt; destination);\n    public void ExportMLDsaPrivateKey(Span&lt;byte&gt; destination);\n    public void ExportMLDsaPrivateSeed(Span&lt;byte&gt; destination);\n\n    \/\/ Standard key container format exports\n    public byte[] ExportSubjectPublicKeyInfo();\n    public byte[] ExportPkcs8PrivateKey();\n    public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan&lt;byte&gt; passwordBytes, PbeParameters pbeParameters);\n    public string ExportSubjectPublicKeyInfoPem();\n    public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan&lt;char&gt; password, PbeParameters pbeParameters);\n\n    \/\/ Operations the algorithm can perform\n    public void SignData(ReadOnlySpan&lt;byte&gt; data, Span&lt;byte&gt; destination, ReadOnlySpan&lt;byte&gt; context = default);\n    public void SignMu(ReadOnlySpan&lt;byte&gt; externalMu, Span&lt;byte&gt; destination);\n    public void SignPreHash(ReadOnlySpan&lt;byte&gt; hash, Span&lt;byte&gt; destination, string hashAlgorithmOid, ReadOnlySpan&lt;byte&gt; context = default);\n    public bool VerifyData(ReadOnlySpan&lt;byte&gt; data, ReadOnlySpan&lt;byte&gt; signature, ReadOnlySpan&lt;byte&gt; context = default);\n    public bool VerifyMu(ReadOnlySpan&lt;byte&gt; externalMu, ReadOnlySpan&lt;byte&gt; signature);\n    public bool VerifyPreHash(ReadOnlySpan&lt;byte&gt; hash, ReadOnlySpan&lt;byte&gt; signature, string hashAlgorithmOid, ReadOnlySpan&lt;byte&gt; context = default);\n\n    \/\/ Key exports, implementation-specific.    \n    protected abstract void ExportMLDsaPrivateSeedCore(Span&lt;byte&gt; destination);\n    protected abstract void ExportMLDsaPublicKeyCore(Span&lt;byte&gt; destination);\n    protected abstract void ExportMLDsaPrivateKeyCore(Span&lt;byte&gt; destination);\n    protected abstract bool TryExportPkcs8PrivateKeyCore(Span&lt;byte&gt; destination, out int bytesWritten);\n\n    \/\/ Algorithm operations, implementation-specific.\n    protected abstract void SignDataCore(ReadOnlySpan&lt;byte&gt; data, ReadOnlySpan&lt;byte&gt; context, Span&lt;byte&gt; destination);\n    protected abstract void SignMuCore(ReadOnlySpan&lt;byte&gt; externalMu, Span&lt;byte&gt; destination);\n    protected abstract void SignPreHashCore(ReadOnlySpan&lt;byte&gt; hash, ReadOnlySpan&lt;byte&gt; context, string hashAlgorithmOid, Span&lt;byte&gt; destination);\n    protected abstract bool VerifyDataCore(ReadOnlySpan&lt;byte&gt; data, ReadOnlySpan&lt;byte&gt; context, ReadOnlySpan&lt;byte&gt; signature);\n    protected abstract bool VerifyMuCore(ReadOnlySpan&lt;byte&gt; externalMu, ReadOnlySpan&lt;byte&gt; signature);\n    protected abstract bool VerifyPreHashCore(ReadOnlySpan&lt;byte&gt; hash, ReadOnlySpan&lt;byte&gt; context, string hashAlgorithmOid, ReadOnlySpan&lt;byte&gt; signature);\n}<\/code><\/pre>\n<p>Let\u2019s first see how this stacks up against our goals:<\/p>\n<ol>\n<li><img data-opt-id=1871002211  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/> All of the instance methods are about \u201cthe key\/keypair\u201d, none are about \u201cthe algorithm\u201d.\n<ul>\n<li>Generating and importing keys are <code>static<\/code> methods.<\/li>\n<\/ul>\n<\/li>\n<li><img data-opt-id=1871002211  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/> You can\u2019t tell from the class shape, but the base class tracks disposal and won\u2019t call any virtual members once the key is disposed.<\/li>\n<li><img data-opt-id=1871002211  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/> It turns out ML-DSA and ML-KEM have very little in common. And while ML-DSA and Composite ML-DSA sound similar, they differ in important ways.\n<ul>\n<li>So all of the new algorithms directly extend <code>object<\/code>.<\/li>\n<\/ul>\n<\/li>\n<li><img data-opt-id=1871002211  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/> The class extensively uses the Template Method Pattern. All argument and state validation is done in the base class\u2019s <code>public<\/code> methods, the <code>protected abstract<\/code> methods only have to do the last step.<\/li>\n<li><img data-opt-id=1871002211  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/> <code>RSA<\/code> and <code>ECDsa<\/code> both have a method named <code>SignData<\/code> that takes the full data to sign and produces a signature.  <code>MLDsa<\/code> matches that.\n<ul>\n<li><code>MLDsa<\/code>\u2018s version gains an extra <code>context<\/code> parameter from the specification, but that doesn\u2019t fundamentally change the terminology.<\/li>\n<li>Additionally, all of the Export methods from <code>AsymmetricAlgorithm<\/code> are here, with the same parameters, in the same order.<\/li>\n<\/ul>\n<\/li>\n<li><img data-opt-id=1871002211  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/> <code>RSA<\/code> and <code>ECDsa<\/code> both have a method named <code>SignHash<\/code>, it produces a signature that is compatible with <code>SignData<\/code>.  ML-DSA\u2019s <code>HashML-DSA<\/code> variant produces an intentionally <em>incompatible<\/em> signature, so instead of <code>SignHash<\/code> it\u2019s called <code>SignPreHash<\/code>.\n<ul>\n<li><code>SignMu<\/code> is closer to <code>SignHash<\/code>, but it\u2019s still different. And it\u2019s very different from <code>SignPreHash<\/code>, so it needed an even more unique name.<\/li>\n<\/ul>\n<\/li>\n<li><img data-opt-id=1871002211  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/> There are no <code>abstract<\/code> or <code>virtual<\/code> methods that operate on arrays.  The <code>MLDsa<\/code> base class does have a lot of overloads that accept (or return) arrays, but those are just for caller convenience.<\/li>\n<\/ol>\n<p>One thing that may stand out is the prevalence of <code>void<\/code> methods writing to spans.<br \/>\nFor ML-DSA, ML-KEM, and SLH-DSA, all of the algorithm operations have fixed-size responses;<br \/>\nthat means there was a strong case made for \u201cif you pass a buffer that\u2019s not exactly the correct size, you\u2019re holding it wrong.\u201d<br \/>\nOkay, so how do you know how to hold it right?<br \/>\nClearly, just open <a href=\"https:\/\/nvlpubs.nist.gov\/nistpubs\/FIPS\/NIST.FIPS.204.pdf\">FIPS 204 (Module-Lattice-Based Digital Signature Standard)<\/a>,<br \/>\njump down to section 4 (Parameter Sets), and read Table 2 (Sizes (in bytes) of keys and signatures of ML-DSA)<\/p>\n<table>\n<thead>\n<tr>\n<th><\/th>\n<th>Private Key<\/th>\n<th>Public Key<\/th>\n<th>Signature Size<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>ML-DSA-44<\/td>\n<td>2560<\/td>\n<td>1312<\/td>\n<td>2420<\/td>\n<\/tr>\n<tr>\n<td>ML-DSA-65<\/td>\n<td>4032<\/td>\n<td>1952<\/td>\n<td>3309<\/td>\n<\/tr>\n<tr>\n<td>ML-DSA-87<\/td>\n<td>4896<\/td>\n<td>2592<\/td>\n<td>4627<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Just kidding.<\/p>\n<p>The data from this table (as well as other data) is in the <code>Algorithm<\/code> property:<\/p>\n<pre><code class=\"language-csharp\">namespace System.Security.Cryptography;\n\npublic sealed partial class MLDsaAlgorithm : IEquatable&lt;MLDsaAlgorithm&gt;\n{\n    public static MLDsaAlgorithm MLDsa44 { get; }\n    public static MLDsaAlgorithm MLDsa65 { get; }\n    public static MLDsaAlgorithm MLDsa87 { get; }\n\n    public string Name { get; }\n\n    public int MuSizeInBytes { get; }\n    public int PrivateKeySizeInBytes { get; }\n    public int PrivateSeedSizeInBytes { get; }\n    public int PublicKeySizeInBytes { get; }\n    public int SignatureSizeInBytes { get; }\n}<\/code><\/pre>\n<p>Every now and then, someone wants\/needs to do interop with the underlying provider.<br \/>\nSo, we still have the Cng and OpenSsl derived types, but they\u2019re much, much smaller.<\/p>\n<pre><code class=\"language-csharp\">namespace System.Security.Cryptography;\n\npublic sealed partial class MLDsaCng : MLDsa\n{\n    public MLDsaCng(CngKey key) : base (GetMLDsaAlgorithm(key)) { }\n\n    public CngKey GetKey();\n\n    protected override void Dispose(bool disposing);\n    protected override void ExportMLDsaPrivateKeyCore(Span&lt;byte&gt; destination);\n    protected override void ExportMLDsaPrivateSeedCore(Span&lt;byte&gt; destination);\n    protected override void ExportMLDsaPublicKeyCore(Span&lt;byte&gt; destination);\n    protected override void SignDataCore(ReadOnlySpan&lt;byte&gt; data, ReadOnlySpan&lt;byte&gt; context, Span&lt;byte&gt; destination);\n    protected override void SignMuCore(ReadOnlySpan&lt;byte&gt; externalMu, Span&lt;byte&gt; destination);\n    protected override void SignPreHashCore(ReadOnlySpan&lt;byte&gt; hash, ReadOnlySpan&lt;byte&gt; context, string hashAlgorithmOid, Span&lt;byte&gt; destination);\n    protected override bool TryExportPkcs8PrivateKeyCore(Span&lt;byte&gt; destination, out int bytesWritten);\n    protected override bool VerifyDataCore(ReadOnlySpan&lt;byte&gt; data, ReadOnlySpan&lt;byte&gt; context, ReadOnlySpan&lt;byte&gt; signature);\n    protected override bool VerifyMuCore(ReadOnlySpan&lt;byte&gt; externalMu, ReadOnlySpan&lt;byte&gt; signature);\n    protected override bool VerifyPreHashCore(ReadOnlySpan&lt;byte&gt; hash, ReadOnlySpan&lt;byte&gt; context, string hashAlgorithmOid, ReadOnlySpan&lt;byte&gt; signature);\n}\n\npublic sealed partial class MLDsaOpenSsl : MLDsa\n{\n    public MLDsaOpenSsl(SafeEvpPKeyHandle pkeyHandle) : base (GetMLDsaAlgorithm(pkeyHandle)) { }\n\n    public SafeEvpPKeyHandle DuplicateKeyHandle();\n\n    protected override void Dispose(bool disposing);\n    protected override void ExportMLDsaPrivateKeyCore(Span&lt;byte&gt; destination);\n    protected override void ExportMLDsaPrivateSeedCore(Span&lt;byte&gt; destination);\n    protected override void ExportMLDsaPublicKeyCore(Span&lt;byte&gt; destination);\n    protected override void SignDataCore(ReadOnlySpan&lt;byte&gt; data, ReadOnlySpan&lt;byte&gt; context, Span&lt;byte&gt; destination);\n    protected override void SignMuCore(ReadOnlySpan&lt;byte&gt; externalMu, Span&lt;byte&gt; destination);\n    protected override void SignPreHashCore(ReadOnlySpan&lt;byte&gt; hash, ReadOnlySpan&lt;byte&gt; context, string hashAlgorithmOid, Span&lt;byte&gt; destination);\n    protected override bool TryExportPkcs8PrivateKeyCore(Span&lt;byte&gt; destination, out int bytesWritten);\n    protected override bool VerifyDataCore(ReadOnlySpan&lt;byte&gt; data, ReadOnlySpan&lt;byte&gt; context, ReadOnlySpan&lt;byte&gt; signature);\n    protected override bool VerifyMuCore(ReadOnlySpan&lt;byte&gt; externalMu, ReadOnlySpan&lt;byte&gt; signature);\n    protected override bool VerifyPreHashCore(ReadOnlySpan&lt;byte&gt; hash, ReadOnlySpan&lt;byte&gt; context, string hashAlgorithmOid, ReadOnlySpan&lt;byte&gt; signature);\n}<\/code><\/pre>\n<p>And that is our last change from the existing types: there\u2019s no \u201cimport a key into MLDsaCng\u201d, or \u201cgenerate a key with MLDsaCng\u201d.<br \/>\nWhy? The primary reason is that you shouldn\u2019t care.<br \/>\nMLDsaCng doesn\u2019t work on Linux, MLDsaOpenSsl doesn\u2019t work on Windows;<br \/>\nso if you\u2019re writing a run-anywhere app or library, you want to stick to just using the base class.<br \/>\nIf you\u2019re trying to work with the underlying provider, that work should be done using the provider classes, like <code>CngKey.Create(...)<\/code>.<\/p>\n<h2>Does This Help Me As An Implementer?<\/h2>\n<p>OK, it\u2019s pretty unusual that someone other than us extends cryptographic key types, but it happens.<br \/>\nThe answer is, emphatically, \u201cyes!\u201d<\/p>\n<p>For <code>RSAOpenSsl<\/code> (and the hidden class used by <code>RSA.Create()<\/code> on Linux), signing looks like this:<\/p>\n<pre><code class=\"language-csharp\">public override bool TrySignHash(\n    ReadOnlySpan&lt;byte&gt; hash,\n    Span&lt;byte&gt; destination,\n    HashAlgorithmName hashAlgorithm,\n    RSASignaturePadding padding,\n    out int bytesWritten)\n{\n    ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm));\n    ArgumentNullException.ThrowIfNull(padding);\n    ThrowIfDisposed();\n\n    SafeEvpPKeyHandle key = GetKey();\n    int bytesRequired = Interop.Crypto.GetEvpPKeySizeBytes(key);\n\n    if (destination.Length &lt; bytesRequired)\n    {\n        bytesWritten = 0;\n        return false;\n    }\n\n    bytesWritten = Interop.Crypto.RsaSignHash(key, padding.Mode, hashAlgorithm, hash, destination);\n    Debug.Assert(bytesWritten == bytesRequired);\n    return true;\n}<\/code><\/pre>\n<p>Argument validation, a disposed state check, a precondition on the destination size to prevent an out-of-bounds write when calling the provider implementation, and then finally the call to the provider.<\/p>\n<p>Here\u2019s the same for <code>MLDsaOpenSsl<\/code>:<\/p>\n<pre><code class=\"language-csharp\">protected override void SignDataCore(ReadOnlySpan&lt;byte&gt; data, ReadOnlySpan&lt;byte&gt; context, Span&lt;byte&gt; destination) =&gt;\n    Interop.Crypto.MLDsaSignPure(_key, data, context, destination);<\/code><\/pre>\n<p>\u201cBut you can hide all manner of sins behind a one-line call\u201d.  OK, here\u2019s <code>MLDsaSignPure<\/code>:<\/p>\n<pre><code class=\"language-csharp\">internal static void MLDsaSignPure(\n    SafeEvpPKeyHandle pkey,\n    ReadOnlySpan&lt;byte&gt; msg,\n    ReadOnlySpan&lt;byte&gt; context,\n    Span&lt;byte&gt; destination)\n{\n    int ret = CryptoNative_MLDsaSignPure(\n        pkey, GetExtraHandle(pkey),\n        msg, msg.Length,\n        context, context.Length,\n        destination, destination.Length);\n\n    if (ret != 1)\n    {\n        throw Interop.Crypto.CreateOpenSslCryptographicException();\n    }\n}<\/code><\/pre>\n<p>The only thing <code>MLDsaOpenSsl.SignDataCore<\/code> needs to do is <em>call OpenSSL<\/em>,<br \/>\neverything else was done in the base class.<\/p>\n<p>For <code>RSA<\/code>, every single derived type gets independently tested for<\/p>\n<ul>\n<li>Argument validation<\/li>\n<li>Disposed state<\/li>\n<li>Argument validation vs Disposed state ordering<\/li>\n<li>Buffer too small<\/li>\n<\/ul>\n<p>just to make sure they\u2019re consistent.<\/p>\n<p>For <code>MLDsa<\/code>, it\u2019s impossible for them to be inconsistent, so we only need to test the base class.<\/p>\n<p><code>RSA<\/code>-derived types also get tested with a correct buffer, and an overly large buffer, when doing \u201calgorithm correctness\u201d tests.<br \/>\nFor <code>MLDsa<\/code>-derived types, that\u2019s almost the entirety of the tests we run.<\/p>\n<p>So, overall it\u2019s less code to write, is therefore less error-prone, and by eliminating categories of tests it makes the overall testing phase faster (with no loss of coverage).<br \/>\nSounds like a win.<\/p>\n<p>There is one drawback in testing, and that\u2019s that we want a separation between \u201ctesting the <code>MLDsa<\/code> base class behaviors\u201d and \u201ctesting <code>MLDsa<\/code> implementations\u201d (so we can actually run fewer tests overall),<br \/>\nbut the most obvious name for each of those halves is <code>MLDsaTests<\/code>.<br \/>\nWhile all of the algorithms use the same strategy of a <code>static class<\/code> to test things like \u201cno abstract or virtual methods are called once the instance is disposed\u201d,<br \/>\nand an <code>abstract class<\/code> to make sure that the implementation types are performing the algorithms correctly,<br \/>\nwe ended up with four different naming patterns for four algorithms (none of which are clearly superior to the others):<\/p>\n<table>\n<thead>\n<tr>\n<th>Algorithm<\/th>\n<th>static test class<\/th>\n<th>instance test class<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>ML-DSA<\/td>\n<td>MLDsaTests<\/td>\n<td>MLDsaTestsBase<\/td>\n<\/tr>\n<tr>\n<td>ML-KEM<\/td>\n<td>MLKemTests<\/td>\n<td>MLKemBaseTests<\/td>\n<\/tr>\n<tr>\n<td>SLH-DSA<\/td>\n<td>SlhDsaContractTests<\/td>\n<td>SlhDsaTests<\/td>\n<\/tr>\n<tr>\n<td>Composite ML-DSA<\/td>\n<td>CompositeMLDsaContractTests<\/td>\n<td>CompositeMLDsaTestsBase<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>What\u2019s Up With <code>[Experimental]<\/code>?<\/h2>\n<p>In .NET Cryptography, we observe a modified \u201crule-of-two\u201d:<br \/>\nwe don\u2019t (usually) add an algorithm unless two (or more) of our supported OSes offer it.<br \/>\nThis helps us to reduce situations where we\u2019ve designed something that can\u2019t be fulfilled by a new OS, or an OS that added the feature later.<br \/>\nWhen I was in college, math students used the phrase \u201cengineering induction\u201d for the notion of \u201cif it works three times, it\u2019ll work forever\u201d (versus the much more rigid mathematical induction we had to use in formal proofs).<br \/>\nOur \u201crule-of-two\u201d is like that\u2026 except with two instead of three: if an algorithm feature is exposed by any two of Windows, OpenSSL, or macOS, it\u2019ll <em>probably<\/em> work on the third.<\/p>\n<p>Since Windows hasn\u2019t yet (as of this writing) added support for SLH-DSA,<br \/>\nand neither Windows nor OpenSSL have added Composite ML-DSA as a first-class algorithm,<br \/>\nwe\u2019ve decided to release the <code>SlhDsa<\/code> and <code>CompositeMLDsa<\/code> classes with <code>[Experimental]<\/code> on the classes themselves.<br \/>\nIt\u2019s possible (though not expected) that we\u2019ll have to make breaking structural changes to these classes when the OS support arrives.<\/p>\n<p>For <code>MLKem<\/code> and <code>MLDsa<\/code> we\u2019ve removed <code>[Experimental]<\/code> from the classes,<br \/>\nbut it remains on a few methods:<\/p>\n<pre><code class=\"language-csharp\">using System.Security.Cryptography;\n\npublic abstract partial class MLKem : System.IDisposable\n{\n    \/\/ These are [Experimental]\n    [Experimental(\"SYSLIB5006\", UrlFormat=\"https:\/\/aka.ms\/dotnet-warnings\/{0}\")]\n    public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan&lt;byte&gt; passwordBytes, PbeParameters pbeParameters);\n    [Experimental(\"SYSLIB5006\", UrlFormat=\"https:\/\/aka.ms\/dotnet-warnings\/{0}\")]\n    public byte[] ExportPkcs8PrivateKey();\n    [Experimental(\"SYSLIB5006\", UrlFormat=\"https:\/\/aka.ms\/dotnet-warnings\/{0}\")]\n    public byte[] ExportSubjectPublicKeyInfo();\n    [Experimental(\"SYSLIB5006\", UrlFormat=\"https:\/\/aka.ms\/dotnet-warnings\/{0}\")]\n    public static MLKem ImportFromPem(ReadOnlySpan&lt;char&gt; source);\n    ...\n\n    \/\/ These are not\n    public byte[] ExportPrivateSeed();\n    public static MLKem GenerateKey(MLKemAlgorithm algorithm);\n    public static MLKem ImportDecapsulationKey(MLKemAlgorithm algorithm, byte[] source);\n    public void Encapsulate(Span&lt;byte&gt; ciphertext, Span&lt;byte&gt; sharedSecret);\n    public void Decapsulate(ReadOnlySpan&lt;byte&gt; ciphertext, Span&lt;byte&gt; sharedSecret);\n    ...\n}<\/code><\/pre>\n<p>\u201cWhat\u2019s the difference?\u201d you ask? Mainly spec ownership.<\/p>\n<p>All of the parts of the class that come from <a href=\"https:\/\/nvlpubs.nist.gov\/nistpubs\/FIPS\/NIST.FIPS.203.pdf\">FIPS 203<\/a> are in a published spec,<br \/>\nhave been written for both Windows and OpenSSL, and we\u2019ve integrated with them.<br \/>\nTherefore, there are no surprises left there, and so no need for <code>[Experimental]<\/code>.<\/p>\n<p>The PKCS#8 PrivateKeyInfo and X.509 SubjectPublicKeyInfo formats of the key, however,<br \/>\ncome from a different spec (in this case, \u201cdraft-ietf-lamps-kyber-certificates\u201d).<br \/>\nWhen our last possible day for changes came up, the specification had not yet been published.<br \/>\nWhile draft-11 looks like it will be published as the finished RFC,<br \/>\nwe still needed to make callers aware that these formats were still susceptible to both breaking changes and interoperability concerns.<br \/>\nIf draft-11 is published as the RFC (or was by the time you read this), you can just suppress the diagnostic.<\/p>\n<p>It is very similar for <code>MLDsa<\/code>, except that, additionally, <code>SignPreHash<\/code> and <code>VerifyPreHash<\/code> are <code>[Experimental]<\/code> even though they come from <a href=\"https:\/\/nvlpubs.nist.gov\/nistpubs\/FIPS\/NIST.FIPS.204.pdf\">NIST FIPS 204<\/a>.<br \/>\nThat\u2019s mainly because we feel that representing the hash algorithm by name isn\u2019t <em>quite<\/em> right, and representing it by OID is not very user friendly<br \/>\n(\u201cSHAKE-128\u201d is a XOF, which means it doesn\u2019t have a fixed output length; \u201c2.16.840.1.101.3.4.2.11\u201d means \u201ca 256-bit extraction from SHAKE-128\u201d).<br \/>\nWe\u2019re also not certain if we should be validating that the pre-hash length matches the hash algorithm output,<br \/>\nor if \u201cgarbage in, garbage out\u201d is the correct design.<br \/>\nUltimately, these both tie back to the \u201crule-of-two\u201d, because while OpenSSL 3.5 supports HashML-DSA,<br \/>\nit\u2019s not as polished as pure ML-DSA,<br \/>\nand we\u2019re waiting on them (and the rest of the ecosystem) for some future guidance.<\/p>\n<h2>Where Does .NET Use These Algorithms?<\/h2>\n<p>These new algorithms can be used a few places within the <code>System.Security.Cryptography<\/code> namespaces:<\/p>\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.security.cryptography.x509certificates.certificaterequest\"><code>CertificateRequest<\/code><\/a>: <code>MLDsa<\/code>, <code>SlhDsa<\/code>, <code>CompositeMLDsa<\/code><\/li>\n<li><code>SignedCms<\/code>\u2018 <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.security.cryptography.pkcs.cmssigner\"><code>CmsSigner<\/code><\/a>: <code>MLDsa<\/code>, <code>SlhDsa<\/code><\/li>\n<li>COSE\u2019s <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.security.cryptography.cose.cosesigner\"><code>CoseSigner<\/code><\/a>: <code>MLDsa<\/code>\n<ul>\n<li><code>CoseSigner<\/code> doesn\u2019t accept an <code>MLDsa<\/code> directly.  The COSE library added a new <code>CoseKey<\/code> type to reduce the number of overloads required for key-specific verification, and <code>CoseKey<\/code> can accept an <code>MLDsa<\/code> instance.<\/li>\n<li><a href=\"https:\/\/datatracker.ietf.org\/doc\/draft-ietf-cose-sphincs-plus\/\">COSE with SLH-DSA<\/a> is an expired draft specification, so we left it out.  Based on current trends, it\u2019ll probably turn up for .NET 11, but that\u2019s not a promise.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><code>CertificateRequest<\/code> accepting PQC keys may have already been a hint,<br \/>\nbut just like .NET\u2019s <code>X509Certificate2<\/code> instances can know about <code>RSA<\/code>, <code>DSA<\/code>, <code>ECDsa<\/code>, and <code>ECDiffieHellman<\/code> private keys,<br \/>\nthey can also know about <code>MLDsa<\/code>, <code>SlhDsa<\/code>, and <code>CompositeMLDsa<\/code> private keys.<br \/>\n<code>X509Certificate2<\/code> can also track <code>MLKem<\/code> private keys,<br \/>\nbut since <code>MLKem<\/code> can\u2019t self-sign it isn\u2019t tightly integrated with <code>CertificateRequest<\/code>.<\/p>\n<p>Most .NET APIs that accept an <code>X509Certificate2<\/code> (or even <code>X509Certificate<\/code>) are interested in signing,<br \/>\nso ML-KEM subject keys inside a certificate can\u2019t be used with things like TLS.<br \/>\nCertificates with ML-DSA public keys, though, should generally work.<br \/>\nThe two most prominent places are <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.net.security.sslstreamcertificatecontext\"><code>SslStreamCertificateContext<\/code><\/a> (and <code>SslStream<\/code> directly) and <code>SignedCms<\/code>\u2018 <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.security.cryptography.pkcs.cmssigner\"><code>CmsSigner<\/code><\/a> (which works either with attached keys, or detached keys, which is why it was also mentioned above).<\/p>\n<p>Sometimes layering requires explicit support at each layer for a new algorithm.<br \/>\nFor example, Kestrel\u2019s <code>CertificateConfigLoader<\/code> required <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/62866\">a change to support ML-DSA and SLH-DSA<\/a>.<br \/>\nWe fixed everywhere that we noticed, but if we missed somewhere (for an algorithm that makes sense) we\u2019ll (probably) fix it in a servicing update.<\/p>\n<p>For SslStream to work with ML-DSA or SLH-DSA certificates you need to be using TLS 1.3 (or a newer future version),<br \/>\nand the OS needs to support it,<br \/>\nand so does the other half of the connection.<\/p>\n<h2>Great, How Do I Get Started?<\/h2>\n<ul>\n<li>Go grab a version of <a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\/10.0\">.NET 10<\/a><\/li>\n<li>Ensure you\u2019re on a computer where the OS supports the algorithms.\n<ul>\n<li>We\u2019ll tell you if yours is via <code>System.Security.Cryptography.MLDsa.IsSupported<\/code> (or similar for <code>MLKem<\/code>, <code>SlhDsa<\/code>, et al).<\/li>\n<li>For Linux, you need OpenSSL 3.5 or newer<\/li>\n<li>Windows support <a href=\"https:\/\/aka.ms\/PQCnowGAIgniteBlog\">arrived this month<\/a>, so if you\u2019re running Windows 11 and have rebooted for Patch Tuesday, you should be good to go.<\/li>\n<\/ul>\n<\/li>\n<li>If you\u2019re targeting .NET Standard 2.0, you will need to reference a 10.0 version of <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Bcl.Cryptography\/\">Microsoft.Bcl.Cryptography<\/a><\/li>\n<\/ul>\n<pre><code class=\"language-csharp\">using System.Security.Cryptography;\n\nif (!MLKem.IsSupported)\n{\n    Console.WriteLine(\"ML-KEM isn't supported :(\");\n    return;\n}\n\nMLKemAlgorithm alg = MLKemAlgorithm.MLKem768;\n\nusing (MLKem privateKey = MLKem.GenerateKey(alg))\nusing (MLKem publicKey = MLKem.ImportEncapsulationKey(alg, privateKey.ExportEncapsulationKey()))\n{\n    publicKey.Encapsulate(out byte[] ciphertext, out byte[] sharedSecret1);\n    byte[] sharedSecret2 = privateKey.Decapsulate(ciphertext);\n\n    if (sharedSecret1.AsSpan().SequenceEqual(sharedSecret2))\n    {\n        Console.WriteLine($\"Same answer, yay math! {Convert.ToHexString(sharedSecret1)}\");\n    }\n    else\n    {\n        Console.WriteLine(\"You just got the one in 2^165 failure. There's probably a prize for that.\");\n        Console.WriteLine($\"sharedSecret1: {Convert.ToHexString(sharedSecret1)}\");\n        Console.WriteLine($\"sharedSecret2: {Convert.ToHexString(sharedSecret2)}\");\n        Console.WriteLine($\"MLKEM768 seed: {Convert.ToHexString(privateKey.ExportPrivateSeed())}\");\n    }\n}<\/code><\/pre>\n<p>If you run into any surprises, <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\">let us know<\/a>!<\/p>\n<h2>Special Thanks<\/h2>\n<p>As the saying goes, it takes a village (to raise a child).<br \/>\nWe wouldn\u2019t have made it as far as we did, at the quality we did, without help.<\/p>\n<ul>\n<li>GitHub Security Services: Participating in the class design journey, doing all of the work for ML-KEM, and setting up a private CI leg to get the project off to a good start.<\/li>\n<li>OpenSSL, Debian (13), CentOS (10): The timing of the OpenSSL 3.5 release, and how rapidly Debian and CentOS adopted it, meant we had stable CI coverage way earlier than we expected.<\/li>\n<li>Windows Cryptography: For putting PQC into the Windows Insider builds, and being responsive to our feedback.<\/li>\n<li>IETF LAMPS-WG: For quickly responding to our questions and feedback for the composite signatures project.<\/li>\n<\/ul>\n<p>The post <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/post-quantum-cryptography-in-dotnet\/\">Post-Quantum Cryptography in .NET<\/a> appeared first on <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\">.NET Blog<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>The main focus of .NET Cryptography for .NET 10 was adding support for Post-Quantum Cryptography (PQC). Cryptography work tends to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":94,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[7],"tags":[],"class_list":["post-2837","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet"],"_links":{"self":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/2837","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/comments?post=2837"}],"version-history":[{"count":0,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/2837\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media\/94"}],"wp:attachment":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media?parent=2837"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/categories?post=2837"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/tags?post=2837"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}