Skip to content

Instantly share code, notes, and snippets.

@lovromazgon
Forked from Chase-san/Rabbit.java
Last active February 9, 2019 15:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lovromazgon/007acc8a96a5f65c7194 to your computer and use it in GitHub Desktop.
Save lovromazgon/007acc8a96a5f65c7194 to your computer and use it in GitHub Desktop.
Rabbit Stream Cipher
/**
* @see {@link http://tools.ietf.org/rfc/rfc4503.txt}
*/
public class Rabbit {
private static final int[] A = new int[] { 0x4D34D34D, 0xD34D34D3, 0x34D34D34, 0x4D34D34D, 0xD34D34D3, 0x34D34D34, 0x4D34D34D, 0xD34D34D3 };
private static final long MAX_UNSIGNED_INT = Integer.MAX_VALUE * 2l + 2; //2^32
private static final boolean DEBUG = false;
private int[] X;
private int[] C;
private byte b;
private byte[] keyStream;
private int keyIndex;
public Rabbit() {
reset();
}
public void reset() {
b = 0;
X = new int[8];
C = new int[8];
keyStream = null;
keyIndex = 0;
}
public byte[] crypt(byte[] data) {
byte[] result = new byte[data.length];
for (int i = 0; i < data.length; i++) {
if (keyStream == null || keyIndex == 16) {
extractKeyStream();
keyIndex = 0;
if (DEBUG) {
System.out.println("Key Stream:");
for (byte k : keyStream) {
System.out.print(Integer.toHexString(Byte.toUnsignedInt(k)) + " ");
}
System.out.println();
}
}
result[i] = (byte) ((data[i] ^ keyStream[keyIndex++]) & 0xff);
}
return result;
}
public void extractKeyStream() {
nextState();
keyStream = new byte[16];
int temp;
for (int j = 0; j < 8; j++) {
if (j%2 == 0) {
temp = ((X[6-j] >> 16) & 0xFFFF) ^ (X[(9-j) % 8] & 0xFFFF);
}
else {
temp = (X[7-j] & 0xFFFF) ^ ((X[(12-j) % 8] >> 16) & 0xFFFF);
}
keyStream[2*j] = (byte)((temp >> 8) & 0xFF);
keyStream[2*j + 1] = (byte)(temp & 0xFF);
}
}
public void setupKey(byte[] input) {
if (input.length != 16) {
throw new IllegalArgumentException("I need a 128-bit key");
}
int[] key = new int[8];
for (int j = 0; j < 8; j++) {
key[-j+7] = (Byte.toUnsignedInt(input[2*j]) << 8) | (Byte.toUnsignedInt(input[2*j+1]));
}
for (int j = 0; j < 8; j++) {
if (j%2 == 0) {
X[j] = key[(j+1) % 8] << 16 | key[j];
C[j] = key[(j+4) % 8] << 16 | key[(j+5) % 8];
}
else {
X[j] = key[(j+5) % 8] << 16 | key[(j+4) % 8];
C[j] = key[j] << 16 | key[(j+1) % 8];
}
}
nextState();
nextState();
nextState();
nextState();
for (int j = 0; j < 8; j++) {
C[j] = C[j] ^ X[(j+4) % 8];
}
if (DEBUG) {
System.out.println("After key setup:");
printX();
printC();
}
}
public void setupIV(byte[] input) {
if (input.length != 8) {
throw new IllegalArgumentException("I need a 64-bit iv");
}
int[] iv = new int[4];
for (int j = 0; j < 4; j++) {
iv[3-j] = (Byte.toUnsignedInt(input[2*j]) << 8) | (Byte.toUnsignedInt(input[2*j+1]));
}
for (int j = 0; j < 8; j++) {
C[j] ^= iv[j%4 == 1 ? 3 : (9-j)%4] << 16 | iv[j%4 == 3 ? 0 : j%4] & 0xFFFF;
}
nextState();
nextState();
nextState();
nextState();
if (DEBUG) {
System.out.println("After iv setup:");
printX();
printC();
}
}
private void nextState() {
updateCounter();
int G[] = new int[8];
for(int j = 0; j < 8; j++) {
long t = X[j] + C[j] & 0xFFFFFFFFL;
G[j] = (int)(((t * t) ^ ((t * t) >> 32)) % MAX_UNSIGNED_INT);
}
for (int j = 0; j< 8; j++) {
int j1 = (((j-1) % 8) + 8) % 8;
int j2 = (((j-2) % 8) + 8) % 8;
if (j%2 == 0) {
X[j] = G[j] + rotl(G[j1], 16) + rotl(G[j2], 16);
}
else {
X[j] = G[j] + rotl(G[j1], 8) + G[j2];
}
}
}
private void updateCounter() {
for (int j = 0; j < 8; j++) {
long temp = (C[j] & 0xFFFFFFFFL) + (A[j] & 0xFFFFFFFFL) + b;
b = (temp >= MAX_UNSIGNED_INT) ? (byte)1 : 0;
C[j] = (int) (temp % MAX_UNSIGNED_INT);
}
}
public void printX() {
System.out.println("------");
for (int i = 0; i < 8; i++) {
System.out.println("X" + i + " = " + Integer.toHexString(X[i]));
}
System.out.println("------");
}
public void printC() {
System.out.println("------");
for (int i = 0; i < 8; i++) {
System.out.println("C" + i + " = " + Integer.toHexString(C[i]));
}
System.out.println("------");
}
private static int rotl(int val, int pas) {
return (val << pas) | (val >>> (32 - pas));
}
}
import org.junit.Assert;
import org.junit.Test;
public class RabbitTest {
private static final byte[] INPUT = convertData(
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " +
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " +
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00");
private static final String[][] TEST_DATA = {
{
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
null,
"B1 57 54 F0 36 A5 D6 EC F5 6B 45 26 1C 4A F7 02 " +
"88 E8 D8 15 C5 9C 0C 39 7B 69 6C 47 89 C6 8A A7 " +
"F4 16 A1 C3 70 0C D4 51 DA 68 D1 88 16 73 D6 96"
},
{
"91 28 13 29 2E 3D 36 FE 3B FC 62 F1 DC 51 C3 AC",
null,
"3D 2D F3 C8 3E F6 27 A1 E9 7F C3 84 87 E2 51 9C " +
"F5 76 CD 61 F4 40 5B 88 96 BF 53 AA 85 54 FC 19 " +
"E5 54 74 73 FB DB 43 50 8A E5 3B 20 20 4D 4C 5E"
},
{
"83 95 74 15 87 E0 C7 33 E9 E9 AB 01 C0 9B 00 43",
null,
"0C B1 0D CD A0 41 CD AC 32 EB 5C FD 02 D0 60 9B " +
"95 FC 9F CA 0F 17 01 5A 7B 70 92 11 4C FF 3E AD " +
"96 49 E5 DE 8B FC 7F 3F 92 41 47 AD 3A 94 74 28"
},
{
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
"00 00 00 00 00 00 00 00",
"C6 A7 27 5E F8 54 95 D8 7C CD 5D 37 67 05 B7 ED " +
"5F 29 A6 AC 04 F5 EF D4 7B 8F 29 32 70 DC 4A 8D " +
"2A DE 82 2B 29 DE 6C 1E E5 2B DB 8A 47 BF 8F 66"
},
{
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
"C3 73 F5 75 C1 26 7E 59",
"1F CD 4E B9 58 00 12 E2 E0 DC CC 92 22 01 7D 6D " +
"A7 5F 4E 10 D1 21 25 01 7B 24 99 FF ED 93 6F 2E " +
"EB C1 12 C3 93 E7 38 39 23 56 BD D0 12 02 9B A7"
},
{
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
"A6 EB 56 1A D2 F4 17 27",
"44 5A D8 C8 05 85 8D BF 70 B6 AF 23 A1 51 10 4D " +
"96 C8 F2 79 47 F4 2C 5B AE AE 67 C6 AC C3 5B 03 " +
"9F CB FC 89 5F A7 1C 17 31 3D F0 34 F0 15 51 CB"
}
};
@Test
public void test() {
Rabbit r = new Rabbit();
int i = 0;
boolean error = false;
for (String[] testData : TEST_DATA) {
r.reset();
byte[] key = convertData(testData[0]);
byte[] iv = convertData(testData[1]);
byte[] expectedOut = convertData(testData[2]);
r.setupKey(key);
if (iv != null) {
r.setupIV(iv);
}
byte[] out = r.crypt(INPUT);
try {
Assert.assertArrayEquals(expectedOut, out);
System.out.println(++i + ": SUCCESS");
} catch (AssertionError e) {
error = true;
System.out.println(++i + ": FAILED");
System.out.println("\tExpected: " + convertData(expectedOut));
System.out.println("\tActual: " + convertData(out));
}
}
if (error) {
Assert.fail("Some tests failed.");
}
}
private static byte[] convertData(String data) {
if (data == null || data.length() == 0) {
return null;
}
byte[] array = new byte[(data.length() + 1) / 3];
int i = 0;
for(String value : data.split(" ")) {
array[i++] = (byte) (Integer.parseInt(value, 16) & 0xFF);
}
return array;
}
private static String convertData(byte[] data) {
StringBuilder sb = new StringBuilder();
for (byte b : data) {
sb.append(" ");
String hex = Integer.toHexString(Byte.toUnsignedInt(b));
if (hex.length() == 1) {
sb.append(" ");
}
sb.append(hex);
}
sb.deleteCharAt(0);
return sb.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment