001: package org.bouncycastle.crypto.engines;
002:
003: import org.bouncycastle.crypto.CipherParameters;
004: import org.bouncycastle.crypto.DataLengthException;
005: import org.bouncycastle.crypto.MaxBytesExceededException;
006: import org.bouncycastle.crypto.StreamCipher;
007: import org.bouncycastle.crypto.params.KeyParameter;
008: import org.bouncycastle.crypto.params.ParametersWithIV;
009: import org.bouncycastle.util.Strings;
010:
011: /**
012: * Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005
013: */
014:
015: public class Salsa20Engine implements StreamCipher {
016: /** Constants */
017: private final static int stateSize = 16; // 16, 32 bit ints = 64 bytes
018:
019: private final static byte[] sigma = Strings
020: .toByteArray("expand 32-byte k"), tau = Strings
021: .toByteArray("expand 16-byte k");
022:
023: /*
024: * variables to hold the state of the engine
025: * during encryption and decryption
026: */
027: private int index = 0;
028: private int[] engineState = new int[stateSize]; // state
029: private int[] x = new int[stateSize]; // internal buffer
030: private byte[] keyStream = new byte[stateSize * 4], // expanded state, 64 bytes
031: workingKey = null, workingIV = null;
032: private boolean initialised = false;
033:
034: /*
035: * internal counter
036: */
037: private int cW0, cW1, cW2;
038:
039: /**
040: * initialise a Salsa20 cipher.
041: *
042: * @param forEncryption whether or not we are for encryption.
043: * @param params the parameters required to set up the cipher.
044: * @exception IllegalArgumentException if the params argument is
045: * inappropriate.
046: */
047: public void init(boolean forEncryption, CipherParameters params) {
048: /*
049: * Salsa20 encryption and decryption is completely
050: * symmetrical, so the 'forEncryption' is
051: * irrelevant. (Like 90% of stream ciphers)
052: */
053:
054: if (!(params instanceof ParametersWithIV)) {
055: throw new IllegalArgumentException(
056: "Salsa20 Init parameters must include an IV");
057: }
058:
059: ParametersWithIV ivParams = (ParametersWithIV) params;
060:
061: byte[] iv = ivParams.getIV();
062:
063: if (iv == null || iv.length != 8) {
064: throw new IllegalArgumentException(
065: "Salsa20 requires exactly 8 bytes of IV");
066: }
067:
068: if (!(ivParams.getParameters() instanceof KeyParameter)) {
069: throw new IllegalArgumentException(
070: "Salsa20 Init parameters must include a key");
071: }
072:
073: KeyParameter key = (KeyParameter) ivParams.getParameters();
074:
075: workingKey = key.getKey();
076: workingIV = iv;
077:
078: setKey(workingKey, workingIV);
079: }
080:
081: public String getAlgorithmName() {
082: return "Salsa20";
083: }
084:
085: public byte returnByte(byte in) {
086: if (limitExceeded()) {
087: throw new MaxBytesExceededException(
088: "2^70 byte limit per IV; Change IV");
089: }
090:
091: if (index == 0) {
092: salsa20WordToByte(engineState, keyStream);
093: engineState[8]++;
094: if (engineState[8] == 0) {
095: engineState[9]++;
096: }
097: }
098: byte out = (byte) (keyStream[index] ^ in);
099: index = (index + 1) & 63;
100:
101: return out;
102: }
103:
104: public void processBytes(byte[] in, int inOff, int len, byte[] out,
105: int outOff) {
106: if (!initialised) {
107: throw new IllegalStateException(getAlgorithmName()
108: + " not initialised");
109: }
110:
111: if ((inOff + len) > in.length) {
112: throw new DataLengthException("input buffer too short");
113: }
114:
115: if ((outOff + len) > out.length) {
116: throw new DataLengthException("output buffer too short");
117: }
118:
119: if (limitExceeded(len)) {
120: throw new MaxBytesExceededException(
121: "2^70 byte limit per IV would be exceeded; Change IV");
122: }
123:
124: for (int i = 0; i < len; i++) {
125: if (index == 0) {
126: salsa20WordToByte(engineState, keyStream);
127: engineState[8]++;
128: if (engineState[8] == 0) {
129: engineState[9]++;
130: }
131: }
132: out[i + outOff] = (byte) (keyStream[index] ^ in[i + inOff]);
133: index = (index + 1) & 63;
134: }
135: }
136:
137: public void reset() {
138: setKey(workingKey, workingIV);
139: }
140:
141: // Private implementation
142:
143: private void setKey(byte[] keyBytes, byte[] ivBytes) {
144: workingKey = keyBytes;
145: workingIV = ivBytes;
146:
147: index = 0;
148: resetCounter();
149: int offset = 0;
150: byte[] constants;
151:
152: // Key
153: engineState[1] = byteToIntLittle(workingKey, 0);
154: engineState[2] = byteToIntLittle(workingKey, 4);
155: engineState[3] = byteToIntLittle(workingKey, 8);
156: engineState[4] = byteToIntLittle(workingKey, 12);
157:
158: if (workingKey.length == 32) {
159: constants = sigma;
160: offset = 16;
161: } else {
162: constants = tau;
163: }
164:
165: engineState[11] = byteToIntLittle(workingKey, offset);
166: engineState[12] = byteToIntLittle(workingKey, offset + 4);
167: engineState[13] = byteToIntLittle(workingKey, offset + 8);
168: engineState[14] = byteToIntLittle(workingKey, offset + 12);
169: engineState[0] = byteToIntLittle(constants, 0);
170: engineState[5] = byteToIntLittle(constants, 4);
171: engineState[10] = byteToIntLittle(constants, 8);
172: engineState[15] = byteToIntLittle(constants, 12);
173:
174: // IV
175: engineState[6] = byteToIntLittle(workingIV, 0);
176: engineState[7] = byteToIntLittle(workingIV, 4);
177: engineState[8] = engineState[9] = 0;
178:
179: initialised = true;
180: }
181:
182: /**
183: * Salsa20 function
184: *
185: * @param input input data
186: *
187: * @return keystream
188: */
189: private void salsa20WordToByte(int[] input, byte[] output) {
190: System.arraycopy(input, 0, x, 0, input.length);
191:
192: for (int i = 0; i < 10; i++) {
193: x[4] ^= rotl((x[0] + x[12]), 7);
194: x[8] ^= rotl((x[4] + x[0]), 9);
195: x[12] ^= rotl((x[8] + x[4]), 13);
196: x[0] ^= rotl((x[12] + x[8]), 18);
197: x[9] ^= rotl((x[5] + x[1]), 7);
198: x[13] ^= rotl((x[9] + x[5]), 9);
199: x[1] ^= rotl((x[13] + x[9]), 13);
200: x[5] ^= rotl((x[1] + x[13]), 18);
201: x[14] ^= rotl((x[10] + x[6]), 7);
202: x[2] ^= rotl((x[14] + x[10]), 9);
203: x[6] ^= rotl((x[2] + x[14]), 13);
204: x[10] ^= rotl((x[6] + x[2]), 18);
205: x[3] ^= rotl((x[15] + x[11]), 7);
206: x[7] ^= rotl((x[3] + x[15]), 9);
207: x[11] ^= rotl((x[7] + x[3]), 13);
208: x[15] ^= rotl((x[11] + x[7]), 18);
209: x[1] ^= rotl((x[0] + x[3]), 7);
210: x[2] ^= rotl((x[1] + x[0]), 9);
211: x[3] ^= rotl((x[2] + x[1]), 13);
212: x[0] ^= rotl((x[3] + x[2]), 18);
213: x[6] ^= rotl((x[5] + x[4]), 7);
214: x[7] ^= rotl((x[6] + x[5]), 9);
215: x[4] ^= rotl((x[7] + x[6]), 13);
216: x[5] ^= rotl((x[4] + x[7]), 18);
217: x[11] ^= rotl((x[10] + x[9]), 7);
218: x[8] ^= rotl((x[11] + x[10]), 9);
219: x[9] ^= rotl((x[8] + x[11]), 13);
220: x[10] ^= rotl((x[9] + x[8]), 18);
221: x[12] ^= rotl((x[15] + x[14]), 7);
222: x[13] ^= rotl((x[12] + x[15]), 9);
223: x[14] ^= rotl((x[13] + x[12]), 13);
224: x[15] ^= rotl((x[14] + x[13]), 18);
225: }
226:
227: int offset = 0;
228: for (int i = 0; i < stateSize; i++) {
229: intToByteLittle(x[i] + input[i], output, offset);
230: offset += 4;
231: }
232:
233: for (int i = stateSize; i < x.length; i++) {
234: intToByteLittle(x[i], output, offset);
235: offset += 4;
236: }
237: }
238:
239: /**
240: * 32 bit word to 4 byte array in little endian order
241: *
242: * @param x value to 'unpack'
243: *
244: * @return value of x expressed as a byte[] array in little endian order
245: */
246: private byte[] intToByteLittle(int x, byte[] out, int off) {
247: out[off] = (byte) x;
248: out[off + 1] = (byte) (x >>> 8);
249: out[off + 2] = (byte) (x >>> 16);
250: out[off + 3] = (byte) (x >>> 24);
251: return out;
252: }
253:
254: /**
255: * Rotate left
256: *
257: * @param x value to rotate
258: * @param y amount to rotate x
259: *
260: * @return rotated x
261: */
262: private int rotl(int x, int y) {
263: return (x << y) | (x >>> -y);
264: }
265:
266: /**
267: * Pack byte[] array into an int in little endian order
268: *
269: * @param x byte array to 'pack'
270: * @param offset only x[offset]..x[offset+3] will be packed
271: *
272: * @return x[offset]..x[offset+3] 'packed' into an int in little-endian order
273: */
274: private int byteToIntLittle(byte[] x, int offset) {
275: return ((x[offset] & 255)) | ((x[offset + 1] & 255) << 8)
276: | ((x[offset + 2] & 255) << 16) | (x[offset + 3] << 24);
277: }
278:
279: private void resetCounter() {
280: cW0 = 0;
281: cW1 = 0;
282: cW2 = 0;
283: }
284:
285: private boolean limitExceeded() {
286: cW0++;
287: if (cW0 == 0) {
288: cW1++;
289: if (cW1 == 0) {
290: cW2++;
291: return (cW2 & 0x20) != 0; // 2^(32 + 32 + 6)
292: }
293: }
294:
295: return false;
296: }
297:
298: /*
299: * this relies on the fact len will always be positive.
300: */
301: private boolean limitExceeded(int len) {
302: if (cW0 >= 0) {
303: cW0 += len;
304: } else {
305: cW0 += len;
306: if (cW0 >= 0) {
307: cW1++;
308: if (cW1 == 0) {
309: cW2++;
310: return (cW2 & 0x20) != 0; // 2^(32 + 32 + 6)
311: }
312: }
313: }
314:
315: return false;
316: }
317: }
|