diff -uNr a/makepgp.py b/makepgp.py --- a/makepgp.py false +++ b/makepgp.py 33671e1f41334901b94ec0fc991efa42f52c1619b332abbc0ced336e9ad15ba270e116bdde43185a80394d31bbbf498549a88f6af2e064de367a8d90d0e3ed07 @@ -0,0 +1,148 @@ +import struct +import time +import sys +import base64 +import math + +openpgp_publickey_format = ">BIB" +mpi_format = ">H" +packet_length_format = ">I" +crc_format = ">I" + +# determine the index of the highest bit set to 1 +def count_bits(B): + R = 0 + i = 0 + while B > 0: + i += 1 + if B & 0x1: + R = i + B >>= 1 + return R + +# convert a number to an array of bytes +# the bytes in the array are stored in big-endian order. +# the most significant byte is stored as the first item +# in the array +def number_to_bytes(B): + R = [] + bits = 0 + while B > 0xff: + bits += 8 + R.append(B & 0xff) + B >>= 8 + R.append(B) + bits += count_bits(B) + return bits, ''.join(map(chr, reversed(R))) + +# An MPI is a byte array that starts with a two byte +# length header. The length is given in bits +def number_to_mpi(B): + C, A = number_to_bytes(B) + return struct.pack(mpi_format, C) + A + +# A PGP public key header consists of a byte "4", +# an integer (4 byte) timestamp and a byte "1" (RSA) +def public_key_header(T): + return struct.pack(openpgp_publickey_format, 4, T, 1) + +# A public key packet is the public key header +# plus 2 MPI numbers, the RSA modulus N and +# the RSA exponent e. +def public_key_packet(t, n, e): + return ''.join((public_key_header(t), number_to_mpi(n), number_to_mpi(e),)) + +# A comment or userid packet is a string encoded as utf-8 +def userid_packet(s): + return s.encode('utf8') + +# The PGP format is a stream of "packets". +# Each packet has a header. This header consists of a tag +# and a length field. The tag has flags to determine if it is a +# "new" or "old" packet. The only supported encoding is "new". +def encode_packet(packet_bytes, tag = 6): + # 0x80, 8th bit always set, 7th bit set --> new packet + h = 0x80 | 0x40 + # 0-5 bits -> the tag + h |= tag + header = chr(h) + + # dude, this is totally how you may save 2 or 3 bytes with minimal complexity + l = len(packet_bytes) + if l < 192: + header += chr(l) + elif l < 8384: + l -= 192 + o1 = l >> 0xff + o2 = l & 0xff + header += chr(o1 + 192) + chr(o2) + else: + header += chr(0xff) + struct.pack(packet_length_format, l) + + return header + packet_bytes + +# When you encode binary data as an ascii text with base64 +# this data becomes fragile. So a CRC code is needed to +# fix this. +def crc24(s): + R = 0xB704CE + for char in s: + B = ord(char) + R ^= B << 16 + for i in range(8): + R <<= 1; + if R & 0x1000000: + R ^= 0x1864CFB + return R & 0xFFFFFF + +# Create a public key for consumption by Phuctor. +# The public key needs to contain 2 packets +# one for the key data (n, e) +# one for the comment +# It must be in the armor / ascii format. +def enarmored_public_key(n, e, comment, t): + R = [] + # the header + R.append("-----BEGIN PGP PUBLIC KEY BLOCK-----") + R.append("") + + # the packets in bytes + A = encode_packet(public_key_packet(t, n, e), 6) + A += encode_packet(userid_packet(comment), 13) + + # the packets in base64 encoding with line length max 76 + s=base64.b64encode(A) + i = 0 + while i < len(s): + R.append(s[i:i+76]) + i += 76 + + # the CRC + R.append("="+base64.b64encode(struct.pack(crc_format, crc24(A))[1:])) + + # the footer + R.append("") + R.append("-----END PGP PUBLIC KEY BLOCK-----") + + return '\n'.join(R) + +# read a file with comma separated lines +# each line is in the TMSR format: e,n,comment +if __name__ == "__main__": + ser = 1 + for x in sys.stdin: + x = x.strip() + + # ignore empty lines + if len(x) == 0 or x.startswith('#'): + continue + + # the comment may contain comma's so split on the first 2 + e,n,comment = x.split(',', 2) + + t0 = int(time.time()) + with open("{0}.txt".format(ser), "wb") as stream: + stream.write(enarmored_public_key(int(n), int(e), comment, t0)) + + ser += 1 +