def buildEncryptedForwardPacket(payload, exitType, exitInfo, path1, path2, key, paddingPRNG=None, secretRNG=None): """Construct a forward message encrypted with the public key of a given user. payload: The payload to deliver. Must be 28K-42b long. exitType: The routing type for the final node. (2 bytes, >=0x100) exitInfo: The routing info for the final node, not including tag. path1: Sequence of ServerInfo objects for the first leg of the path path2: Sequence of ServerInfo objects for the 2nd leg of the path key: Public key of this message's recipient. paddingPRNG: random number generator used to generate padding. If None, a new PRNG is initialized. """ if paddingPRNG is None: paddingPRNG = Crypto.getCommonPRNG() if secretRNG is None: secretRNG = paddingPRNG LOG.debug("Encoding encrypted forward message for %s-byte payload", len(payload)) LOG.debug(" Using path %s/%s", [s.getNickname() for s in path1], [s.getNickname() for s in path2]) LOG.debug(" Delivering to %04x:%r", exitType, exitInfo) # (For encrypted-forward messages, we have overhead for OAEP padding # and the session key, but we save 20 bytes by spilling into the tag.) assert len(payload) == PAYLOAD_LEN - ENC_FWD_OVERHEAD # Generate the session key, and prepend it to the payload. sessionKey = secretRNG.getBytes(SECRET_LEN) payload = sessionKey+payload # We'll encrypt the first part of the new payload with RSA, and the # second half with Lioness, based on the session key. rsaDataLen = key.get_modulus_bytes()-OAEP_OVERHEAD rsaPart = payload[:rsaDataLen] lionessPart = payload[rsaDataLen:] # RSA encryption: To avoid leaking information about our RSA modulus, # we keep trying to encrypt until the MSBit of our encrypted value is # zero. while 1: encrypted = Crypto.pk_encrypt(rsaPart, key) if not (ord(encrypted[0]) & 0x80): break # Lioness encryption. k= Crypto.Keyset(sessionKey).getLionessKeys(Crypto.END_TO_END_ENCRYPT_MODE) lionessPart = Crypto.lioness_encrypt(lionessPart, k) # Now we re-divide the payload into the part that goes into the tag, and # the 28K of the payload proper... payload = encrypted + lionessPart tag = payload[:TAG_LEN] payload = payload[TAG_LEN:] exitInfo = tag + exitInfo assert len(payload) == 28*1024 # And now, we can finally build the message. return _buildPacket(payload, exitType, exitInfo, path1, path2,paddingPRNG)
def buildEncryptedForwardPacket(payload, exitType, exitInfo, path1, path2, key, paddingPRNG=None, secretRNG=None): """Construct a forward message encrypted with the public key of a given user. payload: The payload to deliver. Must be 28K-42b long. exitType: The routing type for the final node. (2 bytes, >=0x100) exitInfo: The routing info for the final node, not including tag. path1: Sequence of ServerInfo objects for the first leg of the path path2: Sequence of ServerInfo objects for the 2nd leg of the path key: Public key of this message's recipient. paddingPRNG: random number generator used to generate padding. If None, a new PRNG is initialized. """ if paddingPRNG is None: paddingPRNG = Crypto.getCommonPRNG() if secretRNG is None: secretRNG = paddingPRNG log.debug("Encoding encrypted forward message for %s-byte payload", len(payload)) log.debug(" Using path %s/%s", [s.getNickname() for s in path1], [s.getNickname() for s in path2]) log.debug(" Delivering to %04x:%r", exitType, exitInfo) # (For encrypted-forward messages, we have overhead for OAEP padding # and the session key, but we save 20 bytes by spilling into the tag.) assert len(payload) == PAYLOAD_LEN - ENC_FWD_OVERHEAD # Generate the session key, and prepend it to the payload. sessionKey = secretRNG.getBytes(SECRET_LEN) payload = sessionKey+payload # We'll encrypt the first part of the new payload with RSA, and the # second half with Lioness, based on the session key. rsaDataLen = key.get_modulus_bytes()-OAEP_OVERHEAD rsaPart = payload[:rsaDataLen] lionessPart = payload[rsaDataLen:] # RSA encryption: To avoid leaking information about our RSA modulus, # we keep trying to encrypt until the MSBit of our encrypted value is # zero. while 1: encrypted = Crypto.pk_encrypt(rsaPart, key) if not (ord(encrypted[0]) & 0x80): break # Lioness encryption. k= Crypto.Keyset(sessionKey).getLionessKeys(Crypto.END_TO_END_ENCRYPT_MODE) lionessPart = Crypto.lioness_encrypt(lionessPart, k) # Now we re-divide the payload into the part that goes into the tag, and # the 28K of the payload proper... payload = encrypted + lionessPart tag = payload[:TAG_LEN] payload = payload[TAG_LEN:] exitInfo = tag + exitInfo assert len(payload) == 28*1024 # And now, we can finally build the message. return _buildPacket(payload, exitType, exitInfo, path1, path2,paddingPRNG)
def _buildHeader(path, secrets, exitType, exitInfo, paddingPRNG): """Helper method to construct a single header. path: A sequence of serverinfo objects. secrets: A list of 16-byte strings to use as master-secrets for each of the subheaders. exitType: The routing type for the last node in the header exitInfo: The routing info for the last node in the header. (Must include 20-byte decoding tag, if any.) paddingPRNG: A pseudo-random number generator to generate padding """ assert len(path) == len(secrets) for info in path: if not info.supportsPacketVersion(): raise MixError("Server %s does not support any recognized packet format." % info.getNickname()) routing, sizes, totalSize = _getRouting(path, exitType, exitInfo) if totalSize > HEADER_LEN: raise MixError("Path cannot fit in header") # headerKey[i]==the AES key object node i will use to decrypt the header headerKeys = [Crypto.Keyset(secret).get(Crypto.HEADER_SECRET_MODE) for secret in secrets] # Length of padding needed for the header paddingLen = HEADER_LEN - totalSize # Calculate junk. # junkSeen[i]==the junk that node i will see, before it does any # encryption. Note that junkSeen[0]=="", because node 0 # sees no junk. junkSeen = [""] for secret, headerKey, size in zip(secrets, headerKeys, sizes): # Here we're calculating the junk that node i+1 will see. # # Node i+1 sees the junk that node i saw, plus the junk that i appends, # all encrypted by i. prngKey = Crypto.Keyset(secret).get(Crypto.RANDOM_JUNK_MODE) # newJunk is the junk that node i will append. (It's as long as # the data that i removes.) newJunk = Crypto.prng(prngKey, size) lastJunk = junkSeen[-1] nextJunk = lastJunk + newJunk # Before we encrypt the junk, we'll encrypt all the data, and # all the initial padding, but not the RSA-encrypted part. # This is equal to - 256 # + sum(size[current]....size[last]) # + paddingLen # This simplifies to: # startIdx = paddingLen - 256 + totalSize - len(lastJunk) startIdx = HEADER_LEN - ENC_SUBHEADER_LEN - len(lastJunk) nextJunk = Crypto.ctr_crypt(nextJunk, headerKey, startIdx) junkSeen.append(nextJunk) # We start with the padding. header = paddingPRNG.getBytes(paddingLen) # Now, we build the subheaders, iterating through the nodes backwards. for i in range(len(path) - 1, -1, -1): rt, ri = routing[i] # Create a subheader object for this node, but don't fill in the # digest until we've constructed the rest of the header. subhead = Subheader(MAJOR_NO, MINOR_NO, secrets[i], None, rt, ri) # placeholder for as-yet-uncalculated digest # Do we need to include some of the remaining header in the # RSA-encrypted portion? underflowLength = subhead.getUnderflowLength() if underflowLength > 0: underflow = header[:underflowLength] header = header[underflowLength:] else: underflow = "" # Do we need to spill some of the routing info out from the # RSA-encrypted portion? If so, prepend it. header = subhead.getOverflow() + header # Encrypt the symmetrically encrypted part of the header header = Crypto.ctr_crypt(header, headerKeys[i]) # What digest will the next server see? subhead.digest = Crypto.sha1(header + junkSeen[i]) # Encrypt the subheader, plus whatever portion of the previous header # underflows, into 'esh'. pubkey = path[i].getPacketKey() rsaPart = subhead.pack() + underflow esh = Crypto.pk_encrypt(rsaPart, pubkey) # Concatenate the asymmetric and symmetric parts, to get the next # header. header = esh + header return header
def _buildHeader(path,secrets,exitType,exitInfo,paddingPRNG): """Helper method to construct a single header. path: A sequence of serverinfo objects. secrets: A list of 16-byte strings to use as master-secrets for each of the subheaders. exitType: The routing type for the last node in the header exitInfo: The routing info for the last node in the header. (Must include 20-byte decoding tag, if any.) paddingPRNG: A pseudo-random number generator to generate padding """ assert len(path) == len(secrets) for info in path: if not info.supportsPacketVersion(): raise MixError("Server %s does not support any recognized packet format."%info.getNickname()) routing, sizes, totalSize = _getRouting(path, exitType, exitInfo) if totalSize > HEADER_LEN: raise MixError("Path cannot fit in header") # headerKey[i]==the AES key object node i will use to decrypt the header headerKeys = [ Crypto.Keyset(secret).get(Crypto.HEADER_SECRET_MODE) for secret in secrets ] # Length of padding needed for the header paddingLen = HEADER_LEN - totalSize # Calculate junk. # junkSeen[i]==the junk that node i will see, before it does any # encryption. Note that junkSeen[0]=="", because node 0 # sees no junk. junkSeen = [""] for secret, headerKey, size in zip(secrets, headerKeys, sizes): # Here we're calculating the junk that node i+1 will see. # # Node i+1 sees the junk that node i saw, plus the junk that i appends, # all encrypted by i. prngKey = Crypto.Keyset(secret).get(Crypto.RANDOM_JUNK_MODE) # newJunk is the junk that node i will append. (It's as long as # the data that i removes.) newJunk = Crypto.prng(prngKey,size) lastJunk = junkSeen[-1] nextJunk = lastJunk + newJunk # Before we encrypt the junk, we'll encrypt all the data, and # all the initial padding, but not the RSA-encrypted part. # This is equal to - 256 # + sum(size[current]....size[last]) # + paddingLen # This simplifies to: #startIdx = paddingLen - 256 + totalSize - len(lastJunk) startIdx = HEADER_LEN - ENC_SUBHEADER_LEN - len(lastJunk) nextJunk = Crypto.ctr_crypt(nextJunk, headerKey, startIdx) junkSeen.append(nextJunk) # We start with the padding. header = paddingPRNG.getBytes(paddingLen) # Now, we build the subheaders, iterating through the nodes backwards. for i in range(len(path)-1, -1, -1): rt, ri = routing[i] # Create a subheader object for this node, but don't fill in the # digest until we've constructed the rest of the header. subhead = Subheader(MAJOR_NO, MINOR_NO, secrets[i], None, #placeholder for as-yet-uncalculated digest rt, ri) # Do we need to include some of the remaining header in the # RSA-encrypted portion? underflowLength = subhead.getUnderflowLength() if underflowLength > 0: underflow = header[:underflowLength] header = header[underflowLength:] else: underflow = "" # Do we need to spill some of the routing info out from the # RSA-encrypted portion? If so, prepend it. header = subhead.getOverflow() + header # Encrypt the symmetrically encrypted part of the header header = Crypto.ctr_crypt(header, headerKeys[i]) # What digest will the next server see? subhead.digest = Crypto.sha1(header+junkSeen[i]) # Encrypt the subheader, plus whatever portion of the previous header # underflows, into 'esh'. pubkey = path[i].getPacketKey() rsaPart = subhead.pack() + underflow esh = Crypto.pk_encrypt(rsaPart, pubkey) # Concatenate the asymmetric and symmetric parts, to get the next # header. header = esh + header return header