TransWikia.com

How to get v12.0 to print PGP public key and secret key BLOCKS after generating a key pair using GenerateAsymmetricKeyPair[]?

Mathematica Asked on July 24, 2021

This question is related to, but not the same as, two others I recently asked about using Mathematica’s PGP functionality: "After using GenerateAsymmetricKeyPair[], how to print the two keys in hex?" and "Given a PGP public key, how can I extract its PGP fingerprint in hex, using v12.0?". As before, I am using Mathematica v12.0.

Having generated a PGP key pair using GenerateAsymmetricKeyPair[], how can I print the two key blocks in the format in which they are usually stated, i.e. broken into 64-character lines in base64, like this:

-----BEGIN PGP PUBLIC KEY BLOCK-----
[optional version info]
[blank line]
[several 64-character lines in base64]
[5-character final line in base64, beginning with "="]
-----END PGP PUBLIC KEY BLOCK-----

This is the standard format if you want to send someone your public key in the body text of an email, for example.

A private key block looks similar:

-----BEGIN PGP PRIVATE KEY BLOCK-----
[optional version info]
[blank line]
[several 64-character lines in base64]
[a line in base64 of possibly fewer than 64 characters, ending with "=="]
[5-character final line in base64, beginning with "="]
-----END PGP PUBLIC KEY BLOCK-----

I realise that since the base64 string in the public key block contains other information as well as the public key in the strict sense – and similarly for the private key block, which in fact contains the public key as well as the private key in the strict sense – some information (including name, email address, and passphrase if specified) may need to be processed by Mathematica before GenerateAsymmetricKeyPair[] is called, and if not before then it certainly will after. But how can I actually get Mathematica to produce key blocks in this standard format?

[Edit by @ool: This was not a great description by me of what’s in the armoured ASCII versions of the two keys. The canonical source here is section 11 of RFC 4880, which specifies that minimal blocks in each case are constructed from the relevant key packet (public or private) followed by a userid packet, with the combination of those two items then followed by a CRC24 checksum; and which advocates that the userid packet in the private key block "should" be followed by a self-signature because that will allow the extraction of the public key, while commenting that self-signature is not necessary, for example when a transferable public key accompanies the transferable private key.]

One Answer

UPDATE: Partial implementation of RFC 2440 to generate a Public Key ASCII armor block from GenerateAsymmetricKeyPair[]. The Base64 data will include 3 types of packets: Public Key, User ID, Signature. RFC 2440 can be quite confusing, particularly concerning the signature and the fields to hash and encrypt. I cannot guarantee the accuracy of the code. The purpose is to show the extent of what is involved.

Some functions:

crc24[bytes_] := (
  (* create a BitVector *)
  
  crcBV = CreateDataStructure["BitVector", 24];
  
  (* initialize the bit vector with 0xb704ce *)
  
  initial = {1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 
    0, 1, 1, 1, 0};
  k = 23;
  Map[If[# == 1, crcBV["BitSet", k--], k--] &, initial];
  
  (* polynome info *)
  
  poly = {23, 18, 17, 14, 11, 10, 7, 6, 5, 4, 3, 1};
  
  (* process each bit of data *)
  Map[
   (b = #;
     i = 7;
     While[i >= 0,
      If[BitAnd[b, BitShiftLeft[1, i]] != 0, v = 1, v = 0];
      xorResult = BitXor[crcBV["BitGet", 23], v];
      k = 23;
      While[k > 0,
       If[MemberQ[poly, k],
        (* true *)
        
        bx = BitXor[crcBV["BitGet", k - 1], xorResult];
        If[bx != 0, crcBV["BitSet", k], crcBV["BitClear", k]],
        (* false *)
        
        If[crcBV["BitGet", k - 1] != 0, crcBV["BitSet", k], 
          crcBV["BitClear", k]];
        ];
       k--;
       ];
      If[xorResult != 0, crcBV["BitSet", 0], crcBV["BitClear", 0]];
      i--
      ]) &
   , bytes // Normal];
  
  bitList = Map[crcBV["BitGet", #] &, Flatten@Range[23, 0, -1]];
  pList = Partition[bitList, 8];
  BaseEncode@
   ByteArray@
    Map[(#[[8]] + #[[7]]*2 + #[[6]]*4 + #[[5]]*8 + #[[4]]*16 + 
#[[3]]*32 + #[[2]]*64 + #[[1]]*128) &, pList]
  )

toBitList[bytes_ByteArray] := (
  Flatten@Map[IntegerDigits[#, 2, 8] &, bytes // Normal]
  )

toInteger[bytes_ByteArray] := (
  Total[MapIndexed[#1*(2^(#2 - 1)) &, 
     Reverse[toBitList[bytes]]]][[1]]
  )

toByteArray[int_Integer] := (
  digits2 = IntegerDigits[int, 2];
  padded = 
   PadLeft[digits2, 
    Mod[8 - Mod[Length[digits2], 8], 8] + Length[digits2]];
  ByteArray[Map[FromDigits[#, 2] &, Partition[padded, 8]]]
  )

toMultiprecision[bytes_ByteArray] := (
  bl = BitLength[toInteger[bytes]];
  part = Partition[IntegerDigits[bl, 2, 16], 8];
  map = Map[(#[[8]] + #[[7]]*2 + #[[6]]*4 + #[[5]]*8 + #[[4]]*16 + 
#[[3]]*32 + #[[2]]*64 + #[[1]]*128) &, part];
  ByteArray[Join[map, bytes // Normal]]
  )

toMultiprecisionByteArray[int_Integer] := (
  toMultiprecision[toByteArray[int]]
  )

addPacketHeader[tagType_String, body_ByteArray] := (
  bodyLength = Length[body];
  encodedLength = toByteArray[bodyLength];
  If[Length[encodedLength] == 3, 
   encodedLength = Prepend[encodedLength // Normal, 0]];
  blength = BitLength[bodyLength];
  Which[
   tagType == "PublicKey",
   tag = Which[blength <= 8, 2^^10011000, blength <= 16, 2^^10011001, 
     blength > 16, 2^^10011010],
   
   tagType == "UserID",
   tag = Which[blength <= 8, 2^^10110100, blength <= 16, 2^^10110101, 
     blength > 16, 2^^10110110],
   
   tagType == "Signature",
   tag = Which[blength <= 8, 2^^10001000, blength <= 16, 2^^10001001, 
     blength > 16, 2^^10001010],

   tagType == "PrivateKey", 
   tag = Which[blength <= 8, 2^^10010100, blength <= 16, 2^^10010101, 
    blength > 16, 2^^10010110]
   ];
  Join[{tag}, encodedLength, body]
  )

addSubPacketHeader[tagType_String, body_ByteArray] := (
  bodyLength = Length[body] + 1; (* length includes the type byte *)
 
   encodedLength = toByteArray[bodyLength];
  If[Length[encodedLength] == 4, 
   encodedLength = Prepend[encodedLength // Normal, 0]];
  If[Length[encodedLength] == 3, 
   encodedLength = Flatten@Prepend[encodedLength // Normal, {0, 0}]];
  blength = BitLength[bodyLength];
  Which[
   tagType == "SignatureCreationTime",
   type = 2,
   
   tagType == "KeyID",
   type = 16;
   
   ];
  Join[encodedLength, {type}, body]
  )

addHashedOrNoneSubPacketHeader[body_] := (
  bodyLength = Length[body];
  encodedLength = toByteArray[bodyLength];
  If[Length[encodedLength] == 1, 
   encodedLength = Prepend[encodedLength // Normal, 0]];
  Join[encodedLength, body]
  )

getUserIDPacketForSignature[uID_ByteArray] := (
  length = Length[uID];
  length32 = 
   Map[(#[[8]] + #[[7]]*2 + #[[6]]*4 + #[[5]]*8 + #[[4]]*16 + 
#[[3]]*32 + #[[2]]*64 + #[[1]]*128) &, 
    Partition[IntegerDigits[length, 2, 32], 8]];
  ByteArray[Join[{16^^b4}, length32, userID]]
  )

getTrailerData[sigData_] := (
  length = Length[sigData];
  length32 = 
   Map[(#[[8]] + #[[7]]*2 + #[[6]]*4 + #[[5]]*8 + #[[4]]*16 + 
#[[3]]*32 + #[[2]]*64 + #[[1]]*128) &, 
    Partition[IntegerDigits[length, 2, 32], 8]];
  ByteArray[Join[{4}, {16^^FF}, length32]]
  )

Key creation:

keyPair = GenerateAsymmetricKeyPair[]

The code:

(* =============== Public Key Packet ============= *)
rsa = 1;
version = 4;
timeOfCreation = 
  toByteArray[
   QuantityMagnitude[
    DateDifference[{1970, 1, 1}, {2021, 4, 9}, "Second"]]];
publicKeyAlgorithm = rsa; 
mpPublicByteArray = 
  toMultiprecision[keyPair["PublicKey"]["PublicByteArray"]];
mpPublicExponent = 
  toMultiprecisionByteArray[keyPair["PublicKey"]["PublicExponent"]];
bodyKeyPacket = 
  ByteArray[
   Join[{version}, timeOfCreation, {publicKeyAlgorithm}, 
    mpPublicByteArray, mpPublicExponent]];
keyPacket = addPacketHeader["PublicKey", bodyKeyPacket];

(* ============= Fingerprint and Key ID =========[Equal]*)

fingerprint = Hash[ByteArray@keyPacket, "SHA", "ByteArray"];
keyID = Take[fingerprint, -8];

(* ============[Equal] User ID Packet ============= *)

userID = StringToByteArray["John <[email protected]>"];
userIDPacket = addPacketHeader["UserID", userID];
userIDDataForSignature = getUserIDPacketForSignature[userID];
hashedUserID = Hash[userIDDataForSignature, "SHA", "ByteArray"];

(* ============= Signature Packet ============ *)
sha1 = 2;
signatureVersion = 4;
signatureType = 16; (* certification User ID and Public Key packet *)

signatureAlgorithm = rsa;
hashAlgorithm = sha1;
signatureCreationTime = 
  toByteArray[
   QuantityMagnitude[
    DateDifference[{1970, 1, 1}, {2021, 4, 10}, "Second"]]];
hashedSubPacket1 = 
  addSubPacketHeader["SignatureCreationTime", signatureCreationTime];
hashedSubpackets = addHashedOrNoneSubPacketHeader[hashedSubPacket1];
nonHashedSubPacket1 = addSubPacketHeader["KeyID", keyID];
nonHashedSubPackets = 
  addHashedOrNoneSubPacketHeader[nonHashedSubPacket1];
signatureData = 
  Join[{signatureVersion}, {signatureType}, {signatureAlgorithm}, 
{hashAlgorithm}, hashedSubpackets];
hashedSignatureData = 
  Hash[ByteArray[signatureData], "SHA", "ByteArray"];
highBits16 = Take[hashedSignatureData // Normal, 2];
trailerData = getTrailerData[signatureData];
hashedTrailer = Hash[trailerData, "SHA", "ByteArray"];

(* Encrypted data *)

hashField = 
  Join[fingerprint, hashedUserID, hashedSignatureData, 
   hashedTrailer];
encryptedData = 
  toMultiprecision[Encrypt[keyPair["PrivateKey"], hashField]["Data"]];

signaturePacketBody = 
  ByteArray@
   Join[signatureData, nonHashedSubPackets, highBits16, encryptedData];
signaturePacket = addPacketHeader["Signature", signaturePacketBody];

(* ============= Final block =================== *)

finalArray = 
  ByteArray@Join[keyPacket, userIDPacket, signaturePacket];
pKey64 = BaseEncode[finalArray];
crc24Base64 = crc24[finalArray];
Print["-----BEGIN PGP PUBLIC KEY BLOCK-----n", "Version: OpenPGPn", 
"Comment: Created with Mathematica 12.2n", "n", 
 InsertLinebreaks[pKey64, 
  64], "n", "=", crc24Base64, "n", "-----END PGP PUBLIC KEY 
BLOCK-----"]

The result:

enter image description here

Importation into PGPTool:

enter image description here

Producing a Private Key block, which includes the data for both the public and private key, is a more complicated undertaking, dealing with passphrase, private key encryption, deprecated versions and all. One thing required is the value of p and q, the original prime factors. The following function may assist:

(* extracts the prime numbers p and q from the data generated by 
GenerateAsymetricKeypair[] - required for a Private Key packet*)

getpq[n_, d_, e_] := (
  k = d*e - 1;
  t = 0;
  r = NestWhile[t++; #/2 &, k, EvenQ, 1];
  i = 1;
  While[i < 100,
   g = RandomInteger[{0, n - 1}];
   y = PowerMod[g, r, n];
   If[y == 1 || y == n - 1, Goto[loop]];
   j = 1;
   While[j < t - 1,
    x = PowerMod[y, 2, n];
    If[x == 1, Goto[end]];
    If[x == n - 1, Goto[loop]];
    y = x;
    j++;
    ];
   x = PowerMod[y, 2, n];
   If[x == 1, Goto[end]];
   Label[loop];
   i++;
   ];
  Label[end];
  p = GCD[y - 1, n];
  q = n/p;
  {p, q}
  )

(* usage of the above function and related data required
 to build a Private Key packet *)
{p, q} = getpq[
   keyPair["PublicKey"]["PublicModulus"],
   keyPair["PrivateKey"]["PrivateExponent"],
   keyPair["PublicKey"]["PublicExponent"]
   ];
   u = ModularInverse[p, q];
mpD = toMultiprecision[keyPair["PrivateKey"]["PrivateByteArray"]];
mpP = toMultiprecisionByteArray[p];
mpQ = toMultiprecisionByteArray[q];
mpU = toMultiprecisionByteArray[u];
(* ..... and more *)

ORIGINAL ANSWER (NO LONGER RELEVANT): The example below uses the PublicByteArray representing the key to demonstrate formatting a block. This array is just the PublicModulus converted to a byte array. This byte array is not formatted according to the OpenPGP standard. It is missing several pieces of information that would have to be added for the resulting block to be valid.

Here is an example of producing a block for the public key. First we need a function to produce the crc24 hash. I wanted to play with BitVector (introduced in 12.1) so I created the following (non-optimized) function, which gives the four base64 characters hash. I guess this would not work with 12.0.

crc24[bytes_] := (
  (* create a BitVector *)
  
  crcBV = CreateDataStructure["BitVector", 24];
  
  (* initialize the bit vector with 0xb704ce *)
  
  initial = {1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 
    0, 1, 1, 1, 0};
  k = 23;
  Map[If[# == 1, crcBV["BitSet", k--], k--] &, initial];
  
  (* polynome info *)
  
  poly = {23, 18, 17, 14, 11, 10, 7, 6, 5, 4, 3, 1};
  
  (* process each bit of data *)
  Map[
   (b = #;
     i = 7;
     While[i >= 0,
      If[BitAnd[b, BitShiftLeft[1, i]] != 0, v = 1, v = 0];
      xorResult = BitXor[crcBV["BitGet", 23], v];
      k = 23;
      While[k > 0,
       If[MemberQ[poly, k],
        (* true *)
        
        bx = BitXor[crcBV["BitGet", k - 1], xorResult];
        If[bx != 0, crcBV["BitSet", k], crcBV["BitClear", k]],
        (* false *)
        
        If[crcBV["BitGet", k - 1] != 0, crcBV["BitSet", k], 
          crcBV["BitClear", k]];
        ];
       k--;
       ];
      If[xorResult != 0, crcBV["BitSet", 0], crcBV["BitClear", 0]];
      i--
      ]) &
   , bytes // Normal];
  
  bitList = Map[crcBV["BitGet", #] &, Flatten@Range[23, 0, -1]];
  pList = Partition[bitList, 8];
  BaseEncode@
   ByteArray@
    Map[(#[[8]] + #[[7]]*2 + #[[6]]*4 + #[[5]]*8 + #[[4]]*16 + 
#[[3]]*32 + #[[2]]*64 + #[[1]]*128) &, pList]
  )

The following generates the keys and prints a public key block (with a bogus Version and Comment).

keyPair = GenerateAsymmetricKeyPair[];
pKeyArray = keyPair["PublicKey"]["PublicByteArray"];
pKey64 = BaseEncode[pKeyArray];
crc24Base64 = crc24[pKeyArray];
Print["-----BEGIN PGP PUBLIC KEY BLOCK-----n",
 "Version: OpenPGPn",
 "Comment: Created with Mathematica 12.2n",
 "n",
 InsertLinebreaks[pKey64, 64], "n",
 "=", crc24Base64, "n",
 "-----END PGP PUBLIC KEY BLOCK-----"
 ]

enter image description here

Answered by Jean-Pierre on July 24, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP