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