[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[Freedombox-discuss] Fwd: [Cryptography] Encryption in Trsst



Possibly of interest?

---------- Forwarded message ----------
From: Michael Powers <michael@mpowers.net>
Date: Sat, Mar 15, 2014 at 1:36 PM
Subject: [Cryptography] Encryption in Trsst
To: "cryptography at metzdowd.com" <cryptography at metzdowd.com>


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;
}
}


_______________________________________________
The cryptography mailing list
cryptography at metzdowd.com
http://www.metzdowd.com/mailman/listinfo/cryptography



Reply to: