[Cryptography] Encryption in Trsst

Michael Powers michael at mpowers.net
Sat Mar 15 13:36:15 EDT 2014


We're starting some early field testing of the Trsst Project, and while we're still in need of a security review and models and documentation to support it, I wanted to get some feedback on the encryption piece.

Background: 

Trsst is a convention for using Atom and the Atom Publishing Protocol with XML-Signatures and XML-Encryption as a foundation for building interoperable microblogging services (think: open replacements for Twitter/Facebook) that support decentralized account creation (just generate a keypair), and signed and optionally encrypted public entries.  FAQ is here: https://github.com/TrsstProject/trsst/wiki/Frequently-Asked-Questions 

For a private message, we generate a random 256-bit key and encrypt with AES.  Then for each recipient, we use a hash of the shared ECDH secret and the message-id to encrypt the key and append it to the message.  All public keys are static, and all public and private messages are viewable by anyone.  

Specific questions:

(1) An account is permanently associated with one EC keypair for signing, and another optional keypair for encryption which can changed but probably not often if ever.  How much of a problem would it be to use the same permanent keypair for both signing and encryption?  Moving/copying your content to a new account is the recourse for a lost or compromised key, and isn't terribly costly.

(2) Private messages are not always addressed outside the encryption envelope.  If unaddressed, you have to try each of the keys on a message to see if it is intended for you, which is why we hash the key.  Could the pattern of first half of the encrypted data always being a random key and the second half always being a hash of that key be exploited?  

(3) To ensure you can decrypt your own message, you encode one of the keys for yourself.  Might your private key be compromised when used with your own public key moreso than someone else's public key?

The relevant implementation in Java using Bouncy Castle is below.  The rest of the source is at: https://github.com/TrsstProject/trsst

Many thanks for pointing out issues and errors, glaring or otherwise.

  - Michael

-----

/**
 * Takes the specified 32 bytes, appends its sha-256 digest, and xor
 * encrypts those 64 bytes with the sha-512 hash of the ECDH shared secret
 * and the entry id.
 * 
 * @param input 32 byte key to be encrypted
 * @param publicKey
 * @param privateKey
 * @return
 * @throws SecurityException if unexpected error
 */
public static byte[] encryptKeyWithECDH(byte[] input, long entryId,
		PublicKey publicKey, PrivateKey privateKey)
		throws SecurityException {
	assert input.length == 32; // 256 bit key
	byte[] result = null;
	try {
		KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC");
		keyAgreement.init(privateKey);
		keyAgreement.doPhase(publicKey, true);
		byte[] sharedSecret = keyAgreement.generateSecret();

		// generate 512 bits using shared secret and entry id
		MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
		sha512.update(sharedSecret);
		sha512.update(ByteBuffer.allocate(8).putLong(entryId));
		byte[] sharedHash = sha512.digest();

		// calculate a digest of the input
		byte[] digest = MessageDigest.getInstance("SHA-256").digest(input);

		// xor the key and the digest against the shared hash
		int i;
		result = new byte[64];
		for (i = 0; i < 32; i++) {
			result[i] = (byte) (input[i] ^ sharedHash[i]);
		}
		for (i = 0; i < 32; i++) {
			result[i + 32] = (byte) (digest[i] ^ sharedHash[i + 32]);
		}
	} catch (Exception e) {
		log.error("Error while encrypting element", e);
		throw new SecurityException(e);
	}
	return result;
}

/**
 * Takes the specified 64 byte encoded input and xor decrypts it with the
 * sha-512 hash of the ECDH shared secret and the entry id. Then checks to
 * see if the last 32 bytes is the sha-256 hash of the first 32 bytes. If
 * so, returns the first 32 bytes of the decrypted content. Otherwise, then
 * this key was not intended for us, and returns null.
 * 
 * @param input 64 byte input to be decrypted
 * @param publicKey
 * @param privateKey
 * @return the original 32 byte input, or null if unintended recipient.
 * @throws SecurityException if unexpected error
 */
public static byte[] decryptKeyWithECDH(byte[] input, long entryId,
		PublicKey publicKey, PrivateKey privateKey)
		throws SecurityException {
	assert input.length == 64; // 512 bit encrypted key
	try {
		KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC");
		keyAgreement.init(privateKey);
		keyAgreement.doPhase(publicKey, true);
		byte[] sharedSecret = keyAgreement.generateSecret();

		// generate 512 bits using shared secret and entry id
		MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
		sha512.update(sharedSecret);
		sha512.update(ByteBuffer.allocate(8).putLong(entryId));
		byte[] sharedHash = sha512.digest();

		// xor the key and the digest against the shared hash
		int i;
		byte[] decoded = new byte[64];
		for (i = 0; i < 64; i++) {
			decoded[i] = (byte) (input[i] ^ sharedHash[i]);
		}

		// calculate digest of the decoded key
		MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
		sha256.update(decoded, 0, 32);
		byte[] digest = sha256.digest();

		// verify that the digest of first 32 bytes matches last 32 bytes
		for (i = 0; i < 32; i++) {
			if (digest[i] != decoded[i + 32]) {
				// incorrectly decoded: we're not the intended recipient
				return null;
			}
		}
		return Arrays.copyOfRange(decoded, 0, 32);
	} catch (Exception e) {
		log.error("Error while decrypting element", e);
		throw new SecurityException(e);
	}
}

public static byte[] generateAESKey() {
	byte[] result = new byte[32];
	new SecureRandom().nextBytes(result);
	return result;
}

public static byte[] encryptAES(byte[] input, byte[] key)
		throws InvalidCipherTextException {
	return _cryptBytesAES(input, key, true);
}

public static byte[] decryptAES(byte[] input, byte[] key)
		throws InvalidCipherTextException {
	return _cryptBytesAES(input, key, false);
}

private static byte[] _cryptBytesAES(byte[] input, byte[] key,
		boolean forEncryption) throws InvalidCipherTextException {
	assert key.length == 32; // 32 bytes == 256 bits
	CipherParameters cipherParameters = new KeyParameter(key);
	BlockCipher blockCipher = new AESEngine();
	BlockCipherPadding blockCipherPadding = new ZeroBytePadding();
	BufferedBlockCipher bufferedBlockCipher = new PaddedBufferedBlockCipher(
			blockCipher, blockCipherPadding);
	return process(input, bufferedBlockCipher, cipherParameters,
			forEncryption);
}

private static byte[] process(byte[] input,
		BufferedBlockCipher bufferedBlockCipher,
		CipherParameters cipherParameters, boolean forEncryption)
		throws InvalidCipherTextException {
	bufferedBlockCipher.init(forEncryption, cipherParameters);

	int inputOffset = 0;
	int inputLength = input.length;

	int maximumOutputLength = bufferedBlockCipher
			.getOutputSize(inputLength);
	byte[] output = new byte[maximumOutputLength];
	int outputOffset = 0;
	int outputLength = 0;

	int bytesProcessed;

	bytesProcessed = bufferedBlockCipher.processBytes(input, inputOffset,
			inputLength, output, outputOffset);
	outputOffset += bytesProcessed;
	outputLength += bytesProcessed;

	bytesProcessed = bufferedBlockCipher.doFinal(output, outputOffset);
	outputOffset += bytesProcessed;
	outputLength += bytesProcessed;

	if (outputLength == output.length) {
		return output;
	} else {
		byte[] truncatedOutput = new byte[outputLength];
		System.arraycopy(output, 0, truncatedOutput, 0, outputLength);
		return truncatedOutput;
	}
}

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.metzdowd.com/pipermail/cryptography/attachments/20140315/7bc2db3e/attachment.html>


More information about the cryptography mailing list