raw
tmsr-pgp-genesis        1 import struct
tmsr-pgp-genesis 2 import time
tmsr-pgp-genesis 3 import sys
tmsr-pgp-genesis 4 import base64
tmsr-pgp-genesis 5 import math
tmsr-pgp-genesis 6
tmsr-pgp-genesis 7 openpgp_publickey_format = ">BIB"
tmsr-pgp-genesis 8 mpi_format = ">H"
tmsr-pgp-genesis 9 packet_length_format = ">I"
tmsr-pgp-genesis 10 crc_format = ">I"
tmsr-pgp-genesis 11
tmsr-pgp-genesis 12 # determine the index of the highest bit set to 1
tmsr-pgp-genesis 13 def count_bits(B):
tmsr-pgp-genesis 14 R = 0
tmsr-pgp-genesis 15 i = 0
tmsr-pgp-genesis 16 while B > 0:
tmsr-pgp-genesis 17 i += 1
tmsr-pgp-genesis 18 if B & 0x1:
tmsr-pgp-genesis 19 R = i
tmsr-pgp-genesis 20 B >>= 1
tmsr-pgp-genesis 21 return R
tmsr-pgp-genesis 22
tmsr-pgp-genesis 23 # convert a number to an array of bytes
tmsr-pgp-genesis 24 # the bytes in the array are stored in big-endian order.
tmsr-pgp-genesis 25 # the most significant byte is stored as the first item
tmsr-pgp-genesis 26 # in the array
tmsr-pgp-genesis 27 def number_to_bytes(B):
tmsr-pgp-genesis 28 R = []
tmsr-pgp-genesis 29 bits = 0
tmsr-pgp-genesis 30 while B > 0xff:
tmsr-pgp-genesis 31 bits += 8
tmsr-pgp-genesis 32 R.append(B & 0xff)
tmsr-pgp-genesis 33 B >>= 8
tmsr-pgp-genesis 34 R.append(B)
tmsr-pgp-genesis 35 bits += count_bits(B)
tmsr-pgp-genesis 36 return bits, ''.join(map(chr, reversed(R)))
tmsr-pgp-genesis 37
tmsr-pgp-genesis 38 # An MPI is a byte array that starts with a two byte
tmsr-pgp-genesis 39 # length header. The length is given in bits
tmsr-pgp-genesis 40 def number_to_mpi(B):
tmsr-pgp-genesis 41 C, A = number_to_bytes(B)
tmsr-pgp-genesis 42 return struct.pack(mpi_format, C) + A
tmsr-pgp-genesis 43
tmsr-pgp-genesis 44 # A PGP public key header consists of a byte "4",
tmsr-pgp-genesis 45 # an integer (4 byte) timestamp and a byte "1" (RSA)
tmsr-pgp-genesis 46 def public_key_header(T):
tmsr-pgp-genesis 47 return struct.pack(openpgp_publickey_format, 4, T, 1)
tmsr-pgp-genesis 48
tmsr-pgp-genesis 49 # A public key packet is the public key header
tmsr-pgp-genesis 50 # plus 2 MPI numbers, the RSA modulus N and
tmsr-pgp-genesis 51 # the RSA exponent e.
tmsr-pgp-genesis 52 def public_key_packet(t, n, e):
tmsr-pgp-genesis 53 return ''.join((public_key_header(t), number_to_mpi(n), number_to_mpi(e),))
tmsr-pgp-genesis 54
tmsr-pgp-genesis 55 # A comment or userid packet is a string encoded as utf-8
tmsr-pgp-genesis 56 def userid_packet(s):
tmsr-pgp-genesis 57 return s.encode('utf8')
tmsr-pgp-genesis 58
tmsr-pgp-genesis 59 # The PGP format is a stream of "packets".
tmsr-pgp-genesis 60 # Each packet has a header. This header consists of a tag
tmsr-pgp-genesis 61 # and a length field. The tag has flags to determine if it is a
tmsr-pgp-genesis 62 # "new" or "old" packet. The only supported encoding is "new".
tmsr-pgp-genesis 63 def encode_packet(packet_bytes, tag = 6):
tmsr-pgp-genesis 64 # 0x80, 8th bit always set, 7th bit set --> new packet
tmsr-pgp-genesis 65 h = 0x80 | 0x40
tmsr-pgp-genesis 66 # 0-5 bits -> the tag
tmsr-pgp-genesis 67 h |= tag
tmsr-pgp-genesis 68 header = chr(h)
tmsr-pgp-genesis 69
tmsr-pgp-genesis 70 # dude, this is totally how you may save 2 or 3 bytes with minimal complexity
tmsr-pgp-genesis 71 l = len(packet_bytes)
tmsr-pgp-genesis 72 if l < 192:
tmsr-pgp-genesis 73 header += chr(l)
tmsr-pgp-genesis 74 elif l < 8384:
tmsr-pgp-genesis 75 l -= 192
tmsr-pgp-genesis 76 o1 = l >> 0xff
tmsr-pgp-genesis 77 o2 = l & 0xff
tmsr-pgp-genesis 78 header += chr(o1 + 192) + chr(o2)
tmsr-pgp-genesis 79 else:
tmsr-pgp-genesis 80 header += chr(0xff) + struct.pack(packet_length_format, l)
tmsr-pgp-genesis 81
tmsr-pgp-genesis 82 return header + packet_bytes
tmsr-pgp-genesis 83
tmsr-pgp-genesis 84 # When you encode binary data as an ascii text with base64
tmsr-pgp-genesis 85 # this data becomes fragile. So a CRC code is needed to
tmsr-pgp-genesis 86 # fix this.
tmsr-pgp-genesis 87 def crc24(s):
tmsr-pgp-genesis 88 R = 0xB704CE
tmsr-pgp-genesis 89 for char in s:
tmsr-pgp-genesis 90 B = ord(char)
tmsr-pgp-genesis 91 R ^= B << 16
tmsr-pgp-genesis 92 for i in range(8):
tmsr-pgp-genesis 93 R <<= 1;
tmsr-pgp-genesis 94 if R & 0x1000000:
tmsr-pgp-genesis 95 R ^= 0x1864CFB
tmsr-pgp-genesis 96 return R & 0xFFFFFF
tmsr-pgp-genesis 97
tmsr-pgp-genesis 98 # Create a public key for consumption by Phuctor.
tmsr-pgp-genesis 99 # The public key needs to contain 2 packets
tmsr-pgp-genesis 100 # one for the key data (n, e)
tmsr-pgp-genesis 101 # one for the comment
tmsr-pgp-genesis 102 # It must be in the armor / ascii format.
tmsr-pgp-genesis 103 def enarmored_public_key(n, e, comment, t):
tmsr-pgp-genesis 104 R = []
tmsr-pgp-genesis 105 # the header
tmsr-pgp-genesis 106 R.append("-----BEGIN PGP PUBLIC KEY BLOCK-----")
tmsr-pgp-genesis 107 R.append("")
tmsr-pgp-genesis 108
tmsr-pgp-genesis 109 # the packets in bytes
tmsr-pgp-genesis 110 A = encode_packet(public_key_packet(t, n, e), 6)
tmsr-pgp-genesis 111 A += encode_packet(userid_packet(comment), 13)
tmsr-pgp-genesis 112
tmsr-pgp-genesis 113 # the packets in base64 encoding with line length max 76
tmsr-pgp-genesis 114 s=base64.b64encode(A)
tmsr-pgp-genesis 115 i = 0
tmsr-pgp-genesis 116 while i < len(s):
tmsr-pgp-genesis 117 R.append(s[i:i+76])
tmsr-pgp-genesis 118 i += 76
tmsr-pgp-genesis 119
tmsr-pgp-genesis 120 # the CRC
tmsr-pgp-genesis 121 R.append("="+base64.b64encode(struct.pack(crc_format, crc24(A))[1:]))
tmsr-pgp-genesis 122
tmsr-pgp-genesis 123 # the footer
tmsr-pgp-genesis 124 R.append("")
tmsr-pgp-genesis 125 R.append("-----END PGP PUBLIC KEY BLOCK-----")
tmsr-pgp-genesis 126
tmsr-pgp-genesis 127 return '\n'.join(R)
tmsr-pgp-genesis 128
tmsr-pgp-genesis 129 # read a file with comma separated lines
tmsr-pgp-genesis 130 # each line is in the TMSR format: e,n,comment
tmsr-pgp-genesis 131 if __name__ == "__main__":
tmsr-pgp-genesis 132 ser = 1
tmsr-pgp-genesis 133 for x in sys.stdin:
tmsr-pgp-genesis 134 x = x.strip()
tmsr-pgp-genesis 135
tmsr-pgp-genesis 136 # ignore empty lines
tmsr-pgp-genesis 137 if len(x) == 0 or x.startswith('#'):
tmsr-pgp-genesis 138 continue
tmsr-pgp-genesis 139
tmsr-pgp-genesis 140 # the comment may contain comma's so split on the first 2
tmsr-pgp-genesis 141 e,n,comment = x.split(',', 2)
tmsr-pgp-genesis 142
tmsr-pgp-genesis 143 t0 = int(time.time())
tmsr-pgp-genesis 144 with open("{0}.txt".format(ser), "wb") as stream:
tmsr-pgp-genesis 145 stream.write(enarmored_public_key(int(n), int(e), comment, t0))
tmsr-pgp-genesis 146
tmsr-pgp-genesis 147 ser += 1
tmsr-pgp-genesis 148