/******************************************************************************* * gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/ * Copyright (C) 2014 SVS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package userGeneratedContent.testbedPlugIns.layerPlugIns.layer2recodingScheme.RSA_OAEP_sourceRouting_v0_001; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import staticContent.framework.AnonNode; import staticContent.framework.message.MixMessage; import staticContent.framework.message.Reply; import staticContent.framework.message.Request; import staticContent.framework.routing.RoutingMode; import staticContent.framework.util.Util; public class RSA_OAEP_AES_OFB { private AnonNode owner; private RSA_OAEP_AES_OFB_Config config; private Cipher asymmetricCipher; private Cipher asymmetricCipherReply; private KeyGenerator symKeyGenerator; private KeyGenerator macKeyGenerator; private SecureRandom secureRandom; public RSA_OAEP_AES_OFB(AnonNode owner, RSA_OAEP_AES_OFB_Config config) { this.owner = owner; this.config = config; } public void initAsClient() { // create key generators and ciphers try { this.secureRandom = SecureRandom.getInstance(config.PRNG_ALGORITHM); this.asymmetricCipher = Cipher.getInstance( config.ASYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER ); this.asymmetricCipher.init(Cipher.ENCRYPT_MODE, config.publicKeysOfMixes[config.publicKeysOfMixes.length-1]); //this.minMessageSize = asymmetricCipher.getBlockSize(); this.asymmetricCipherReply = Cipher.getInstance( config.ASYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER ); this.asymmetricCipherReply.init(Cipher.ENCRYPT_MODE, config.publicKeysOfMixes[config.publicKeysOfMixes.length-1]); this.symKeyGenerator = KeyGenerator.getInstance( config.NAME_OF_SYM_KEY_GENERATOR, config.CRYPTO_PROVIDER ); this.symKeyGenerator.init(config.SYM_KEY_LENGTH * 8); this.macKeyGenerator = KeyGenerator.getInstance(config.MAC_ALGORITHM); this.macKeyGenerator.init(config.MAC_KEY_LENGTH * 8); } catch (Exception e) { e.printStackTrace(); } } public void initAsRecoder() { assert owner.ROUTING_MODE == RoutingMode.SOURCE_ROUTING; try { this.asymmetricCipher = Cipher.getInstance(config.ASYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER); this.asymmetricCipher.init(Cipher.DECRYPT_MODE, config.keyPair.getPrivate()); this.asymmetricCipherReply = Cipher.getInstance(config.ASYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER); this.asymmetricCipherReply.init(Cipher.DECRYPT_MODE, config.keyPair.getPrivate()); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("could not init asym cipher at mix"); } } public synchronized Request applyLayeredEncryption(Request request) { try{ if (request.getByteMessage() == null) request.setByteMessage(new byte[0]); if (request.getByteMessage().length == 0) { // dummy System.out.println(owner +" creating dummy"); System.out.println(owner +" config.numberOfMixes: " +config.numberOfMixes); } if (request.getByteMessage().length > config.MAX_PAYLOAD) throw new RuntimeException("can't send more than " +config.MAX_PAYLOAD +" bytes in one message"); // add padding int paddingLength = config.MAX_PAYLOAD - request.getByteMessage().length; byte[] lengthHeader = Util.intToByteArray(request.getByteMessage().length); request.setByteMessage(Util.concatArrays(lengthHeader, request.getByteMessage())); if (paddingLength > 0) { byte[] padding = new byte[paddingLength]; secureRandom.nextBytes(padding); request.setByteMessage(Util.concatArrays(request.getByteMessage(), padding)); } if (owner.IS_DUPLEX) request = addSingleUseReplyBlock(request); SecretKey[] symKeys = new SecretKey[config.routeLength]; IvParameterSpec[] initVectors = new IvParameterSpec[config.routeLength]; byte[][] randomNumbers = new byte[config.routeLength-1][config.ASYM_KEY_LENGTH]; for(int i=0; i<config.routeLength ;i++) { symKeys[i] = symKeyGenerator.generateKey(); byte[] iv = new byte[config.SYM_KEY_LENGTH]; secureRandom.nextBytes(iv); initVectors[i] = new IvParameterSpec(iv); } Cipher symCipher = Cipher.getInstance( config.SYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER ); for(int i=0; i<config.routeLength-1 ;i++) { SecureRandom prng = SecureRandom.getInstance(config.PRNG_ALGORITHM); prng.setSeed(symKeys[i].getEncoded()); prng.nextBytes(randomNumbers[i]); for(int k=i+1; k<config.routeLength-1;k++) { //precompute the decrypt-operation at the mixes (necessary for the MAC) symCipher.init(Cipher.DECRYPT_MODE, symKeys[k], initVectors[k]); randomNumbers[i] = symCipher.doFinal(randomNumbers[i]); } } // add header and encryption layer for each mix byte[] header =null ; byte[] payload = request.getByteMessage() ; for (int i=config.routeLength-1; i>=0; i--) { // generate header (without mac; must be added later) byte[] mac; SecretKey macKey; macKey = macKeyGenerator.generateKey(); // concat header and payload byte[] firstBlock = Util.concatArrays(new byte[][] { macKey.getEncoded(), symKeys[i].getEncoded(), initVectors[i].getIV(), }); if (owner.ROUTING_MODE == RoutingMode.SOURCE_ROUTING) { // add route-info if (i == config.routeLength-1) { // last mix firstBlock = Util.concatArrays(Util.intToByteArray(request.route[i]), firstBlock); } else { firstBlock = Util.concatArrays(Util.intToByteArray(request.route[i+1]), firstBlock); } } // add padding to assure that the message is long enough for the cipher to work byte[] padding = new byte[asymmetricCipher.getBlockSize()-(firstBlock.length + config.MAC_LENGTH)]; secureRandom.nextBytes(padding); firstBlock = Util.concatArrays(firstBlock, padding); if(i==config.routeLength-1) { header = firstBlock; //add Pseudorandom-numbers for(int k=0; k<randomNumbers.length;k++) header = Util.concatArrays(header,randomNumbers[k]); } else { //cut the last block (random number) header = Arrays.copyOfRange(header, 0, header.length-config.ASYM_KEY_LENGTH); header = Util.concatArrays(firstBlock,header); } byte[] plaintext = Util.concatArrays(header, payload); // add mac to header Mac macGenerator = Mac.getInstance(config.MAC_ALGORITHM); macGenerator.init(macKey); mac = macGenerator.doFinal(plaintext); firstBlock = Util.concatArrays(mac, firstBlock); assert macKey.getEncoded().length == config.MAC_KEY_LENGTH; assert symKeys[i].getEncoded().length == config.SYM_KEY_LENGTH; assert initVectors[i].getIV().length == config.IV_LENGTH; assert mac.length == config.MAC_LENGTH; // encrypt message; asymmetric part if (owner.ROUTING_MODE == RoutingMode.GLOBAL_ROUTING) this.asymmetricCipher.init(Cipher.ENCRYPT_MODE, config.publicKeysOfMixes[i]); else { this.asymmetricCipher.init(Cipher.ENCRYPT_MODE, config.publicKeysOfMixes[request.route[i]]); // TODO: possible side-effect -> won't work anymore if mix-ids are chosen differently // System.out.println("" +owner +" using public key " +Util.md5(config.publicKeysOfMixes[request.route[i]].getEncoded()) +"for mix " +request.route[i]); } int pointer = firstBlock.length-config.MAC_LENGTH; firstBlock= asymmetricCipher.doFinal(firstBlock, 0, firstBlock.length); //replace firstBlock plaintext with firstBlock cipherText header = Util.concatArrays(firstBlock, Arrays.copyOfRange(header, pointer, header.length)); // encrypt message; symmetric part symCipher.init(Cipher.ENCRYPT_MODE, symKeys[i], initVectors[i]); payload = symCipher.doFinal(payload, 0, payload.length); if(i!=config.routeLength-1) { pointer = firstBlock.length; byte[] headerCiphertext = firstBlock; for(int k=0; k <config.routeLength-1; k++) { byte[] SymBlock= symCipher.doFinal(header, pointer, config.ASYM_KEY_LENGTH); headerCiphertext = Util.concatArrays(headerCiphertext, SymBlock); pointer+=config.ASYM_KEY_LENGTH; } header = headerCiphertext; } } request.setByteMessage(Util.concatArrays(header,payload)); return request; } catch (Exception e) { e.printStackTrace(); return null; } } public int getMaxPayloadForNextMessage() { return config.MAX_PAYLOAD; } public int getMaxPayloadForNextReply() { return config.MAX_PAYLOAD; } public synchronized Reply extractPayload(Reply reply) { byte[] pseudonym = Arrays.copyOfRange(reply.getByteMessage(), 0, config.PSEUDONYM_LENGTH); byte[] payload = Arrays.copyOfRange(reply.getByteMessage(), config.PSEUDONYM_LENGTH, reply.getByteMessage().length ); byte[] seed; SecureRandom prng = null; SecretKey symKey; IvParameterSpec initVector; Cipher decryptCipher = null; try { seed = config.replySeeds.get(new String(pseudonym,"UTF-8")); prng = SecureRandom.getInstance(config.PRNG_ALGORITHM); prng.setSeed(seed); decryptCipher = Cipher.getInstance(config.SYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER); byte[][] symKeys = new byte[config.numberOfMixes][config.SYM_KEY_LENGTH]; byte[][] IVs= new byte[config.numberOfMixes][config.IV_LENGTH]; for(int i=0; i<config.numberOfMixes;i++) { prng.nextBytes(symKeys[i]); prng.nextBytes(IVs[i]); } for (int i=config.numberOfMixes-1; i>=0; i--) { symKey = new SecretKeySpec(symKeys[i], config.SYM_CRYPTOGRAPHY_ALGORITHM); initVector = new IvParameterSpec(IVs[i]); decryptCipher.init(Cipher.DECRYPT_MODE, symKey, initVector); payload = decryptCipher.doFinal(payload, 0, payload.length); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } // remove Padding byte[] lengthHeader = Arrays.copyOfRange(payload, 0, config.LENGTH_HEADER_LENGTH); int payloadLength = Util.byteArrayToInt(lengthHeader); if (payloadLength == 0) // dummy payload = new byte[0]; else payload = Arrays.copyOfRange(payload, config.LENGTH_HEADER_LENGTH, config.LENGTH_HEADER_LENGTH + payloadLength); reply.setByteMessage(payload); return reply; } private Request addSingleUseReplyBlock(Request request) { /*if (config.DEBUG_ON) System.out.println("Client " +client.getIdentifier() +": generating reply block"); */ try{ byte[] seed; byte[] pseudonym = null; SecureRandom prng = null; // use (seeded) prng to create keys //prng for the sym keys prng = SecureRandom.getInstance(config.PRNG_ALGORITHM); seed = new byte[config.PRNG_SEED_LENGTH]; secureRandom.nextBytes(seed); prng.setSeed(seed); assert prng != null; pseudonym = new byte[config.PSEUDONYM_LENGTH]; // used to identify the correct seed when a reply is received secureRandom.nextBytes(pseudonym); config.replySeeds.put(new String(pseudonym, "UTF-8"), seed); SecretKey[] symKeys = new SecretKey[config.routeLength]; IvParameterSpec[] initVectors = new IvParameterSpec[config.routeLength]; byte[][] randomNumbers = new byte[config.routeLength-1][config.ASYM_KEY_LENGTH]; //generate sym. keys + IV's for(int i=0; i<config.routeLength ;i++) { byte[] symKeyAsByteArray = new byte[config.SYM_KEY_LENGTH]; prng.nextBytes(symKeyAsByteArray); symKeys[i] = new SecretKeySpec(symKeyAsByteArray, config.SYM_CRYPTOGRAPHY_ALGORITHM); byte[] iv = new byte[config.SYM_KEY_LENGTH]; prng.nextBytes(iv); initVectors[i] = new IvParameterSpec(iv); } Cipher symCipher = Cipher.getInstance( config.SYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER ); //generate the pseudo random numbers for(int i=0; i<config.routeLength-1 ;i++) { SecureRandom prng2 = SecureRandom.getInstance(config.PRNG_ALGORITHM); prng2.setSeed(symKeys[i].getEncoded()); prng2.nextBytes(randomNumbers[i]); for(int k=i+1; k<config.routeLength-1;k++) { //precompute the decrypt-operation at the mixes (necessary for the MAC) symCipher.init(Cipher.DECRYPT_MODE, symKeys[k], initVectors[k]); randomNumbers[i] = symCipher.doFinal(randomNumbers[i]); } } byte[] header =null ; for (int i=0; i<=config.routeLength-1; i++){ // generate header (without mac; must be added later) byte[] mac; SecretKey macKey; macKey = macKeyGenerator.generateKey(); // concat header and payload byte[] firstBlock = Util.concatArrays(new byte[][] { macKey.getEncoded(), symKeys[(config.routeLength-1)-i].getEncoded(), initVectors[(config.routeLength-1)-i].getIV(), }); if(i==0) firstBlock = Util.concatArrays(firstBlock, pseudonym); if (owner.ROUTING_MODE == RoutingMode.SOURCE_ROUTING) { // add route-info if (i == 0) { // last mix firstBlock = Util.concatArrays(Util.intToByteArray(request.route[i]), firstBlock); } else { firstBlock = Util.concatArrays(Util.intToByteArray(request.route[i-1]), firstBlock); } } // add padding to assure that the message is long enough for the cipher to work byte[] padding = new byte[asymmetricCipher.getBlockSize()-(firstBlock.length + config.MAC_LENGTH)]; secureRandom.nextBytes(padding); firstBlock = Util.concatArrays(firstBlock, padding); if(i==0) { header = firstBlock; //add Pseudorandom-numbers for(int k=0; k<randomNumbers.length;k++) header = Util.concatArrays(header,randomNumbers[k]); } else { //cut the last block (random number) header = Arrays.copyOfRange(header, 0, header.length-config.ASYM_KEY_LENGTH); header = Util.concatArrays(firstBlock,header); } // add mac to header Mac macGenerator = Mac.getInstance(config.MAC_ALGORITHM); macGenerator.init(macKey); mac = macGenerator.doFinal(header); firstBlock = Util.concatArrays(mac, firstBlock); // encrypt message; asymmetric part if (owner.ROUTING_MODE == RoutingMode.GLOBAL_ROUTING) this.asymmetricCipher.init(Cipher.ENCRYPT_MODE, config.publicKeysOfMixes[i]); else { this.asymmetricCipher.init(Cipher.ENCRYPT_MODE, config.publicKeysOfMixes[request.route[i]]); // TODO: possible side-effect -> won't work anymore if mix-ids are chosen differently // System.out.println("" +owner +" using public key " +Util.md5(config.publicKeysOfMixes[request.route[i]].getEncoded()) +"for mix " +request.route[i]); } int pointer = firstBlock.length-config.MAC_LENGTH; firstBlock= asymmetricCipher.doFinal(firstBlock, 0, firstBlock.length); //replace firstBlock plaintext with firstBlock cipherText header = Util.concatArrays(firstBlock, Arrays.copyOfRange(header, pointer, header.length)); // encrypt message; symmetric part symCipher.init(Cipher.ENCRYPT_MODE, symKeys[(config.routeLength-1)-i], initVectors[(config.routeLength-1)-i]); if(i!=0) { pointer = firstBlock.length; byte[] headerCiphertext = firstBlock; for(int k=0; k <config.routeLength-1; k++) { byte[] SymBlock= symCipher.doFinal(header, pointer, config.ASYM_KEY_LENGTH); headerCiphertext = Util.concatArrays(headerCiphertext, SymBlock); pointer+=config.ASYM_KEY_LENGTH; } header = headerCiphertext; } } byte[] lengthHeader = Util.intToByteArray(header.length); request.setByteMessage( Util.concatArrays(new byte[][] { lengthHeader, header , // == replyBlock request.getByteMessage()//message })); return request; } catch (Exception e) { e.printStackTrace(); return null; } } public synchronized Request recodeMessage(Request message) { synchronized (asymmetricCipher) { boolean isLastMix =false; if(owner.ROUTING_MODE == RoutingMode.GLOBAL_ROUTING) isLastMix = owner.IS_LAST_MIX; String cipherTextHash = null; try { if (config.DEBUG_ON) { cipherTextHash = Util.md5(message.getByteMessage()); // System.out.println("Mix " +owner.getIdentifier() +": received this message (ciphertext): " +Util.md5(message.getByteMessage())); //System.out.println("my public key: " +Util.md5(keyPair.getPublic().getEncoded())); } // decrypt asymmetrically encrypted part byte[] asymPlaintext = asymmetricCipher.doFinal(message.getByteMessage(), 0, asymmetricCipher.getBlockSize()); // extract data from derypted header (= first part of the "asymPlaintext") byte[] mac; SecretKey macKey; SecretKey symKey; IvParameterSpec initVector; int pointer = 0; mac = Arrays.copyOfRange(asymPlaintext, pointer, pointer += config.MAC_LENGTH); if (owner.ROUTING_MODE == RoutingMode.SOURCE_ROUTING) { // determine next hop if source routing is enabled byte[] nextHopAddress = Arrays.copyOfRange(asymPlaintext, pointer, pointer += 4); int address = Util.byteArrayToInt(nextHopAddress); if (address == owner.PUBLIC_PSEUDONYM) { // this mix is the last hop if (owner.DISPLAY_ROUTE_INFO) System.out.println(""+owner +" setting nextHopAddress to \"LAST HOP\""); message.nextHopAddress = MixMessage.NONE; isLastMix= true; } else { // this mix is not the last hop if (owner.DISPLAY_ROUTE_INFO) System.out.println(""+owner +" setting nextHopAddress to " +address); message.nextHopAddress = address; } } byte[] macKeyAsByteArray = Arrays.copyOfRange(asymPlaintext, pointer, pointer += config.MAC_KEY_LENGTH); macKey = new SecretKeySpec(macKeyAsByteArray, config.MAC_ALGORITHM); byte[] symKeyAsByteArray = Arrays.copyOfRange(asymPlaintext, pointer, pointer += config.SYM_KEY_LENGTH); symKey = new SecretKeySpec(symKeyAsByteArray, config.SYM_CRYPTOGRAPHY_ALGORITHM); byte[] ivAsByteArray = Arrays.copyOfRange(asymPlaintext, pointer, pointer += config.IV_LENGTH); initVector = new IvParameterSpec(ivAsByteArray); pointer = asymPlaintext.length; //skip padding // decrypt symmetrically encrypted part Cipher decryptCipher = Cipher.getInstance(config.SYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER); decryptCipher.init(Cipher.DECRYPT_MODE, symKey, initVector); byte[] plaintext; byte[] payloadPlaintext; int payloadPointer = owner.FREE_ROUTE_LENGTH * asymmetricCipher.getBlockSize(); if(!isLastMix) { byte[] headerPlaintext = asymPlaintext ; int blockPointer = asymmetricCipher.getBlockSize(); for(int k=0; k<owner.FREE_ROUTE_LENGTH-1;k++) { byte[] symBlock = decryptCipher.doFinal(message.getByteMessage(), blockPointer, asymmetricCipher.getBlockSize()); headerPlaintext = Util.concatArrays(headerPlaintext, symBlock); blockPointer+= asymmetricCipher.getBlockSize(); } payloadPlaintext = decryptCipher.doFinal(message.getByteMessage(), payloadPointer, message.getByteMessage().length -payloadPointer); plaintext = Util.concatArrays(headerPlaintext, payloadPlaintext); } else { //decrypt payload only payloadPlaintext = decryptCipher.doFinal(message.getByteMessage(), payloadPointer, message.getByteMessage().length-payloadPointer); plaintext = Util.concatArrays(asymPlaintext, Arrays.copyOfRange(message.getByteMessage(), asymmetricCipher.getBlockSize(), payloadPointer)); plaintext = Util.concatArrays(plaintext, payloadPlaintext); } payloadPointer = pointer + (owner.FREE_ROUTE_LENGTH-1)*asymmetricCipher.getBlockSize(); // validate mac Mac macGenerator = Mac.getInstance(config.MAC_ALGORITHM); macGenerator.init(macKey); byte[] signedData = Arrays.copyOfRange(plaintext, config.MAC_LENGTH, plaintext.length); if (config.DEBUG_ON) System.out.println(owner +" " +cipherTextHash +" -> " +Util.md5(signedData)); byte[] locallyGeneratedMac = macGenerator.doFinal(signedData); if (!Arrays.equals(locallyGeneratedMac, mac)) { System.out.println("wrong MAC!"); // TODO return null; } if (config.PERFORM_REPLY_DETECTION) if (config.replayDetection.isReplay(macKeyAsByteArray)) return null; if (isLastMix ) { if(!owner.IS_DUPLEX) { // remove Padding byte[] lengthHeader = Arrays.copyOfRange(plaintext, payloadPointer, payloadPointer += config.LENGTH_HEADER_LENGTH); int payloadLength = Util.byteArrayToInt(lengthHeader); if (payloadLength == 0) // dummy plaintext = new byte[0]; else plaintext = Arrays.copyOfRange(plaintext, payloadPointer, payloadPointer + payloadLength); message.setByteMessage(plaintext); } if (owner.IS_DUPLEX) { // RBlength|replyblock|PTlength|plaintext|padding pointer = payloadPointer; byte[] RBlengthHeader = Arrays.copyOfRange(plaintext, pointer, pointer += config.LENGTH_HEADER_LENGTH); int replyblockLength = Util.byteArrayToInt(RBlengthHeader); byte[] replyblock = Arrays.copyOfRange(plaintext, pointer, pointer += replyblockLength); ArrayList<byte[]> rpList; if(config.replyblocks.containsKey(message.getOwner())) { rpList = config.replyblocks.get(message.getOwner()); } else { rpList = new ArrayList<byte[]>(); } rpList.add(replyblock); config.replyblocks.put(message.getOwner(), rpList); byte[] lengthHeader = Arrays.copyOfRange(plaintext, pointer, pointer += config.LENGTH_HEADER_LENGTH); int payloadLength = Util.byteArrayToInt(lengthHeader); if (payloadLength == 0) // dummy plaintext = new byte[0]; else plaintext = Arrays.copyOfRange(plaintext, pointer, pointer + payloadLength); message.setByteMessage(plaintext); return message; } else return message; } //not last Mix else { byte[] header = Arrays.copyOfRange(plaintext, pointer, payloadPointer); byte[] payload = Arrays.copyOfRange(plaintext,payloadPointer, plaintext.length); SecureRandom prng = SecureRandom.getInstance(config.PRNG_ALGORITHM); prng.setSeed(symKeyAsByteArray); byte[] padding = new byte[config.ASYM_KEY_LENGTH]; prng.nextBytes(padding); header = Util.concatArrays(header,padding); message.setByteMessage(Util.concatArrays(header, payload)); return message; } } catch (Exception e) { e.printStackTrace(); System.err.println(owner +" Exception-message (ciphertext): " +Util.md5(message.getByteMessage())); return null; } } } public Reply recodeReply(Reply message) { synchronized(asymmetricCipherReply) { boolean isLastMix = false; byte[] replyblock; byte[] payload; if(message.getByteMessage().length<=config.MAX_PAYLOAD ) { int paddingLength = config.MAX_PAYLOAD - message.getByteMessage().length; byte[] lengthHeader = Util.intToByteArray(message.getByteMessage().length); message.setByteMessage(Util.concatArrays(lengthHeader, message.getByteMessage())); if (paddingLength > 0) { byte[] padding = new byte[paddingLength]; new SecureRandom().nextBytes(padding); message.setByteMessage(Util.concatArrays(message.getByteMessage(),padding)); } replyblock = config.replyblocks.get(message.getOwner()).remove(0); payload = message.getByteMessage(); } else { replyblock = Arrays.copyOfRange(message.getByteMessage(), 0, message.getByteMessage().length - (config.MAX_PAYLOAD+config.LENGTH_HEADER_LENGTH)); payload = Arrays.copyOfRange(message.getByteMessage(), replyblock.length, message.getByteMessage().length); } try { // decrypt asymmetrically encrypted part byte[] asymPlaintext = asymmetricCipherReply.doFinal(replyblock, 0, asymmetricCipherReply.getBlockSize()); // extract data from derypted header (= first part of the "asymPlaintext") byte[] mac; SecretKey macKey; SecretKey symKey; byte[] pseudonym = null; IvParameterSpec initVector; int pointer = 0; mac = Arrays.copyOfRange(asymPlaintext, pointer, pointer += config.MAC_LENGTH); if (owner.ROUTING_MODE == RoutingMode.SOURCE_ROUTING) { // determine next hop if source routing is enabled byte[] nextHopAddress = Arrays.copyOfRange(asymPlaintext, pointer, pointer += 4); int address = Util.byteArrayToInt(nextHopAddress); if (address == owner.PUBLIC_PSEUDONYM) { // this mix is the last hop if (owner.DISPLAY_ROUTE_INFO) System.out.println(""+owner +" setting nextHopAddress to \"LAST HOP\""); message.nextHopAddress = MixMessage.CLIENT; isLastMix = true; } else { // this mix is not the last hop if (owner.DISPLAY_ROUTE_INFO) System.out.println(""+owner +" setting nextHopAddress to " +address); message.nextHopAddress = address; } } byte[] macKeyAsByteArray = Arrays.copyOfRange(asymPlaintext, pointer, pointer += config.MAC_KEY_LENGTH); macKey = new SecretKeySpec(macKeyAsByteArray, config.MAC_ALGORITHM); byte[] symKeyAsByteArray = Arrays.copyOfRange(asymPlaintext, pointer, pointer += config.SYM_KEY_LENGTH); symKey = new SecretKeySpec(symKeyAsByteArray, config.SYM_CRYPTOGRAPHY_ALGORITHM); byte[] ivAsByteArray = Arrays.copyOfRange(asymPlaintext, pointer, pointer += config.IV_LENGTH); initVector = new IvParameterSpec(ivAsByteArray); if(isLastMix) pseudonym = Arrays.copyOfRange(asymPlaintext, pointer, pointer += config.PSEUDONYM_LENGTH); pointer = asymPlaintext.length; // decrypt symmetrically encrypted part Cipher decryptCipher = Cipher.getInstance(config.SYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER); decryptCipher.init(Cipher.DECRYPT_MODE, symKey, initVector); byte[] plaintext; if(!isLastMix) { byte[] headerPlaintext = asymPlaintext ; int blockPointer = asymmetricCipher.getBlockSize(); for(int k=0; k<owner.FREE_ROUTE_LENGTH-1;k++) { byte[] symBlock = decryptCipher.doFinal(replyblock, blockPointer, asymmetricCipher.getBlockSize()); headerPlaintext = Util.concatArrays(headerPlaintext, symBlock); blockPointer+= asymmetricCipher.getBlockSize(); } plaintext=headerPlaintext; } else //not last mix { plaintext = Util.concatArrays(asymPlaintext, Arrays.copyOfRange(replyblock, asymmetricCipher.getBlockSize(), replyblock.length)); } // validate mac Mac macGenerator = Mac.getInstance(config.MAC_ALGORITHM); macGenerator.init(macKey); byte[] signedData = Arrays.copyOfRange(plaintext, config.MAC_LENGTH, plaintext.length); // if (config.DEBUG_ON) System.out.println(owner +" plaintext: " +Util.md5(signedData) +" (of " +Util.md5(message.getByteMessage()) +")"); byte[] locallyGeneratedMac = macGenerator.doFinal(signedData); if (!Arrays.equals(locallyGeneratedMac, mac)) { System.out.println("wrong MAC!"); // TODO return null; } if (config.PERFORM_REPLY_DETECTION) if (config.replayDetection.isReplay(macKeyAsByteArray)) return null; replyblock = Arrays.copyOfRange(plaintext, pointer, plaintext.length); // encrypt payload with seeded keys Cipher symCipher = Cipher.getInstance( config.SYM_CRYPTOGRAPHY_ALGORITHM, config.CRYPTO_PROVIDER ); symCipher.init(Cipher.ENCRYPT_MODE, symKey, initVector); byte[] payloadCipherText = symCipher.doFinal(payload, 0, payload.length); if(!isLastMix) { SecureRandom prng = SecureRandom.getInstance(config.PRNG_ALGORITHM); prng.setSeed(symKeyAsByteArray); byte[] padding = new byte[config.ASYM_KEY_LENGTH]; prng.nextBytes(padding); replyblock= Util.concatArrays(replyblock,padding); } byte[] cipherText = Util.concatArrays(replyblock, payloadCipherText); if (isLastMix) { cipherText = Util.concatArrays(pseudonym, payloadCipherText); } message.setByteMessage(cipherText); return message; } catch (Exception e) { e.printStackTrace(); return null; } } } }