/********************************************************************* Salsa20example1 version 0.000001 Salsa20 is really simple, even in Java. Here are the operative lines. KeyParameter keyparam = new KeyParameter(str2byt(key)); ParametersWithIV params = new ParametersWithIV(keyparam, nonce); Salsa20 salsa = new Salsa20() salsa.init(true, params); salsa.processBytes(datain, 0, datain.length, dataout, 0); The first two lines set up the key and the nonce (IV). The key must be 16 or 32 bytes (128 or 256 bits). The nonce, aka the initialization vector or IV, must be 8 bytes. After creating an instance, we initialize it and encrypt the data. Note that the encrypt and decrypt operations are identical with Salsa20, that is, the same code that encrypted the data may be used to decrypt it and the last line may be repeated as often as necessary to process large data. Just remember the data chunk size must be a multiple of 64 bytes. It's that simple. The remainder of this program is just lower-level support code needed by Java. And note too that the data need not be padded as is the case with block ciphers. The preferred cryptographic data type is a byte string (byte[] in Java), so a number of conversion functions are provided. This program is a proof-of-concept, a simple example of the streaming cipher, Salsa20, implemented in Java and Bouncy Castle. This is really crude so use it only as an illustrative example. Several test cases have demonstrated interoperability with the Python and C version of Salsa20. There the keys are padded with nulls if shorter than 16 or 32 bytes and truncated if longer so these keys are processed likewise. This example is hardcoded to NOT process input greater than 1000 bytes. To encrypt more you will need to do some extra coding besides just increasing the limit. Larry Bugbee June 2007 *********************************************************************/ import org.bouncycastle.crypto.StreamCipher; import org.bouncycastle.crypto.engines.Salsa20Engine; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.ParametersWithIV; import java.io.File; import java.io.FileInputStream; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.FileOutputStream; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.security.SecureRandom; // ----------------------------------------------------------------------------- public class Salsa20example1 { // this is a class in name only as all its methods are static. // ...yes I know, but this is only a simple example. public static void main(String[] args) { if (args.length < 2) { System.out.println("\n Usage:"); System.out.println( " javac Salsa20example1.java "); System.out.println( " java Salsa20example1 "); System.out.println("\n Bouncy Castle 1.36 or later must be installed. \n"); return; } String filename = args[0]; String key = args[1]; // must be 16 or 32 bytes if (key.length() != 16 && key.length() != 32) { System.out.println("\n *** key must be 16 or 32 bytes, so it will *** "); System.out.println( " *** be padded or truncated as appropriate *** \n"); } key = padnulls(key, 16); // for now, pad and truncate to 16 byte [] nonce = str2byt("12345678"); // must be 8 bytes -- best if random // setup parameters (key and nonce) KeyParameter keyparam = new KeyParameter(str2byt(key)); ParametersWithIV params = new ParametersWithIV(keyparam, nonce); byte[] content = loadfmfile(filename); System.out.println("\nBefore: \n" + byt2str(content)); StreamCipher salsa = new Salsa20Engine(); salsa.init(true, params); byte[] ciphertext = new byte[content.length]; // loop over this next line if you want to process multiple chunks // just be sure chunk size is a multiple of 64 bytes salsa.processBytes(content, 0, content.length, ciphertext, 0); String newfilename = filename+".s20"; savetofile(newfilename, concat(nonce, ciphertext)); byte[] content2 = loadfmfile(newfilename); nonce = extract(content2, 0, 8); ciphertext = extract(content2, 8, content2.length); // setup parameters (key and nonce) KeyParameter keyparam2 = new KeyParameter(str2byt(key)); ParametersWithIV params2 = new ParametersWithIV(keyparam2, nonce); StreamCipher salsa2 = new Salsa20Engine(); salsa2.init(true, params2); byte[] plaintext = new byte[ciphertext.length]; // loop over this next line if you want to process multiple chunks // just be sure chunk size is a multiple of 64 bytes salsa2.processBytes(ciphertext, 0, ciphertext.length, plaintext, 0); System.out.println("\nAfter: \n" + byt2str(plaintext)); if (Arrays.equals(content, plaintext)) { System.out.println("\n *** good *** \n"); } else { System.out.println("\n *** bad *** \n"); } } // ------------------------------------------------------------------------- // helpful utility functions // read a small to medium sized file and return its contents private static byte[] loadfmfile(String filename) { int chunksize = 2000; // <<<<<<<<<<<<<<<<<<<<<<<< int len = 0; byte[] buf = new byte[chunksize]; File file = new File(filename); FileInputStream fis = null; BufferedInputStream bis = null; DataInputStream dis = null; try { fis = new FileInputStream(file); bis = new BufferedInputStream(fis); dis = new DataInputStream(bis); // while (dis.available() != 0) { len = dis.read(buf, 0, chunksize); // } dis.close(); bis.close(); fis.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return extract(buf, 0, len); } // write content to a file private static void savetofile(String newfilename, byte[] content) { int len = content.length; File file = new File(newfilename); FileOutputStream fos = null; BufferedOutputStream bos = null; DataOutputStream dos = null; try { fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos); dos = new DataOutputStream(bos); dos.write(content, 0, len); dos.close(); bos.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } private static String nullstring(int len) { byte zero = 0; byte[] nulls = new byte[len]; Arrays.fill(nulls, zero); return new String(nulls); } private static String padnulls(String str, int len) { return str.concat(nullstring(len)).substring(0, len); } private static String byt2str(byte[] content) { return new String(content); } private static byte[] str2byt(String str) { return str.getBytes(); } private static byte[] concat(byte[] b1, byte[] b2) { byte[] b3 = new byte[b1.length + b2.length]; System.arraycopy(b1, 0, b3, 0, b1.length); System.arraycopy(b2, 0, b3, b1.length, b2.length); return b3; } private static byte[] extract(byte[] b, int fm, int to) { byte[] b2 = new byte[to - fm]; System.arraycopy(b, fm, b2, 0, to - fm); return b2; } } // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------