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: OpenPGP\n", \
"Comment: Created with Mathematica 12.2\n", "\n",
InsertLinebreaks[pKey64,
64], "\n", "=", crc24Base64, "\n", "-----END PGP PUBLIC KEY \
BLOCK-----"]
The result:

Importation into PGPTool:

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: OpenPGP\n",
"Comment: Created with Mathematica 12.2\n",
"\n",
InsertLinebreaks[pKey64, 64], "\n",
"=", crc24Base64, "\n",
"-----END PGP PUBLIC KEY BLOCK-----"
]

gpg
command, egRunProcess[{"gpg", "--help"}]
rather than trying to force Mathematica to do it. It depends on your goals though, just in case this helps you save time :) $\endgroup$ga
is the output ofGenerateAsymetricKeyPair
, thenBaseEncode[ga["PublicKey"]["PublicByteArray"],"Base64"]
returns the key as Base64. In the last line, the = sign is followed by the crc24 hash of the key in Base64 (4 characters). The MathematicaHash
function can compute a crc32 hash, but not crc24. So you would need to create the hash code. $\endgroup$