def _decodeStatelessReplyPayload(payload, tag, userKey): """Decode a (state-carrying) reply payload.""" # Reconstruct the secrets we used to generate the reply block (possibly # too many) seed = Crypto.sha1(tag+userKey+"Generate")[:16] prng = Crypto.AESCounterPRNG(seed) secrets = [ prng.getBytes(SECRET_LEN) for _ in xrange(17) ] return _decodeReplyPayload(payload, secrets, check=1)
def buildReplyBlock(path, exitType, exitInfo, userKey, expiryTime=None, secretRNG=None): """Construct a 'state-carrying' reply block that does not require the reply-message recipient to remember a list of secrets. Instead, all secrets are generated from an AES counter-mode stream, and the seed for the stream is stored in the 'tag' field of the final block's routing info. (See the spec for more info). path: a list of ServerInfo objects exitType,exitInfo: The address to deliver the final message. userKey: a string used to encrypt the seed. NOTE: We used to allow another kind of 'non-state-carrying' reply block that stored its secrets on disk, and used an arbitrary tag to determine which set of secrets to use. """ if secretRNG is None: secretRNG = Crypto.getCommonPRNG() # We need to pick the seed to generate our keys. To make the decoding # step a little faster, we find a seed such that H(seed|userKey|"Validate") # ends with 0. This way, we can detect whether we really have a reply # message with 99.6% probability. (Otherwise, we'd need to repeatedly # lioness-decrypt the payload in order to see whether the message was # a reply.) while 1: seed = _getRandomTag(secretRNG) if Crypto.sha1(seed+userKey+"Validate")[-1] == '\x00': break prng = Crypto.AESCounterPRNG(Crypto.sha1(seed+userKey+"Generate")[:16]) replyBlock, secrets, tag = _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime, prng, seed) STATUS.log("GENERATED_SURB", formatBase64(tag)) return replyBlock