Private/ConvertTo-Pkcs8PrivateKey.ps1
|
function ConvertTo-Pkcs8PrivateKey { <# .SYNOPSIS Exports an RSA private key as PKCS#8 DER on .NET Framework 4.8.1. .DESCRIPTION RSA.ExportPkcs8PrivateKey() was added in .NET Core 3.0 and is unavailable on NetFX 4.8.1. This helper builds the PKCS#8 PrivateKeyInfo structure (RFC 5208) manually from RSA.ExportParameters(): PrivateKeyInfo ::= SEQUENCE { version INTEGER (0), privateKeyAlgorithm AlgorithmIdentifier (rsaEncryption, NULL), privateKey OCTET STRING (PKCS#1 RSAPrivateKey DER) } RSAPrivateKey ::= SEQUENCE { version INTEGER (0), 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 (q^-1 mod p) } DER encoding is done with a minimal local writer (no external dependencies). .PARAMETER Rsa An System.Security.Cryptography.RSA instance containing a private key. .OUTPUTS System.Byte[] — PKCS#8 DER bytes. #> [CmdletBinding()] param( [Parameter(Mandatory)] [System.Security.Cryptography.RSA]$Rsa ) # Minimal DER writer. Handles lengths up to 64KB (sufficient for 4096-bit keys). function Write-DerLength([System.IO.MemoryStream]$Stream, [int]$Length) { if ($Length -lt 0x80) { $Stream.WriteByte([byte]$Length) } elseif ($Length -lt 0x100) { $Stream.WriteByte(0x81) $Stream.WriteByte([byte]$Length) } else { $Stream.WriteByte(0x82) $Stream.WriteByte([byte]($Length -shr 8)) $Stream.WriteByte([byte]($Length -band 0xFF)) } } function Write-DerElement([System.IO.MemoryStream]$Stream, [byte]$Tag, [byte[]]$Value) { $Stream.WriteByte($Tag) Write-DerLength $Stream $Value.Length $Stream.Write($Value, 0, $Value.Length) } function ConvertTo-DerInteger([byte[]]$BigEndianBytes) { # Strip leading zeros, then prepend 0x00 if the high bit would be set # (DER integers are signed two's complement). $i = 0 while ($i -lt ($BigEndianBytes.Length - 1) -and $BigEndianBytes[$i] -eq 0) { $i++ } $trimmed = $BigEndianBytes[$i..($BigEndianBytes.Length - 1)] if ($trimmed[0] -band 0x80) { return ,([byte]0x00) + $trimmed } return ,$trimmed } $params = $Rsa.ExportParameters($true) # Build PKCS#1 RSAPrivateKey $pkcs1 = [System.IO.MemoryStream]::new() $pkcs1Body = [System.IO.MemoryStream]::new() Write-DerElement $pkcs1Body 0x02 ([byte[]]@(0x00)) # version Write-DerElement $pkcs1Body 0x02 (ConvertTo-DerInteger $params.Modulus) # n Write-DerElement $pkcs1Body 0x02 (ConvertTo-DerInteger $params.Exponent) # e Write-DerElement $pkcs1Body 0x02 (ConvertTo-DerInteger $params.D) # d Write-DerElement $pkcs1Body 0x02 (ConvertTo-DerInteger $params.P) # p Write-DerElement $pkcs1Body 0x02 (ConvertTo-DerInteger $params.Q) # q Write-DerElement $pkcs1Body 0x02 (ConvertTo-DerInteger $params.DP) # d mod (p-1) Write-DerElement $pkcs1Body 0x02 (ConvertTo-DerInteger $params.DQ) # d mod (q-1) Write-DerElement $pkcs1Body 0x02 (ConvertTo-DerInteger $params.InverseQ) # q^-1 mod p $pkcs1BodyBytes = $pkcs1Body.ToArray() Write-DerElement $pkcs1 0x30 $pkcs1BodyBytes # SEQUENCE # Build AlgorithmIdentifier: rsaEncryption OID (1.2.840.113549.1.1.1) + NULL $rsaOid = [byte[]]@(0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01) $algId = [System.IO.MemoryStream]::new() $algIdBody = [System.IO.MemoryStream]::new() Write-DerElement $algIdBody 0x06 $rsaOid # OID Write-DerElement $algIdBody 0x05 ([byte[]]@()) # NULL $algIdBodyBytes = $algIdBody.ToArray() Write-DerElement $algId 0x30 $algIdBodyBytes # SEQUENCE # Build PrivateKeyInfo $pkcs8Body = [System.IO.MemoryStream]::new() Write-DerElement $pkcs8Body 0x02 ([byte[]]@(0x00)) # version $pkcs8Body.Write($algId.ToArray(), 0, $algId.Length) # algorithm Write-DerElement $pkcs8Body 0x04 $pkcs1.ToArray() # OCTET STRING $pkcs8 = [System.IO.MemoryStream]::new() Write-DerElement $pkcs8 0x30 $pkcs8Body.ToArray() # SEQUENCE return $pkcs8.ToArray() } |