Skip to content

Commit e4eb99f

Browse files
authored
Add A5 Cipher (#3292)
1 parent 8ca571d commit e4eb99f

File tree

7 files changed

+315
-0
lines changed

7 files changed

+315
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.thealgorithms.ciphers.a5;
2+
3+
import java.util.BitSet;
4+
5+
// https://en.wikipedia.org/wiki/A5/1
6+
public class A5Cipher {
7+
private final A5KeyStreamGenerator keyStreamGenerator;
8+
private static final int KEY_STREAM_LENGTH = 228; // 28.5 bytes so we need to pad bytes or something
9+
10+
public A5Cipher( BitSet sessionKey, BitSet frameCounter ) {
11+
keyStreamGenerator = new A5KeyStreamGenerator();
12+
keyStreamGenerator.initialize( sessionKey, frameCounter );
13+
}
14+
15+
public BitSet encrypt( BitSet plainTextBits ) {
16+
// create a copy
17+
var result = new BitSet( KEY_STREAM_LENGTH );
18+
result.xor( plainTextBits );
19+
20+
var key = keyStreamGenerator.getNextKeyStream();
21+
result.xor( key );
22+
23+
return result;
24+
}
25+
26+
public void resetCounter() {
27+
keyStreamGenerator.reInitialize();
28+
}
29+
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.thealgorithms.ciphers.a5;
2+
3+
import java.util.BitSet;
4+
5+
// TODO: raise exceptions for improper use
6+
public class A5KeyStreamGenerator extends CompositeLFSR {
7+
private BitSet initialFrameCounter;
8+
private BitSet frameCounter;
9+
private BitSet sessionKey;
10+
private static final int INITIAL_CLOCKING_CYCLES = 100;
11+
private static final int KEY_STREAM_LENGTH = 228; // 28.5 bytes so we need to pad bytes or something
12+
13+
@Override
14+
public void initialize( BitSet sessionKey, BitSet frameCounter ) {
15+
this.sessionKey = sessionKey;
16+
this.frameCounter = (BitSet) frameCounter.clone();
17+
this.initialFrameCounter = (BitSet) frameCounter.clone();
18+
registers.clear();
19+
LFSR lfsr1 = new LFSR( 19, 8, new int[]{ 13, 16, 17, 18 } );
20+
LFSR lfsr2 = new LFSR( 22, 10, new int[]{ 20, 21 } );
21+
LFSR lfsr3 = new LFSR( 23, 10, new int[]{ 7, 20, 21, 22 } );
22+
registers.add( lfsr1 );
23+
registers.add( lfsr2 );
24+
registers.add( lfsr3 );
25+
registers.forEach( lfsr -> lfsr.initialize( sessionKey, frameCounter ) );
26+
}
27+
28+
public void reInitialize() {
29+
this.initialize( sessionKey, initialFrameCounter );
30+
}
31+
32+
public BitSet getNextKeyStream() {
33+
for ( int cycle = 1; cycle <= INITIAL_CLOCKING_CYCLES; ++cycle )
34+
this.clock();
35+
36+
BitSet result = new BitSet( KEY_STREAM_LENGTH );
37+
for ( int cycle = 1; cycle <= KEY_STREAM_LENGTH; ++cycle ) {
38+
boolean outputBit = this.clock();
39+
result.set( cycle - 1, outputBit );
40+
}
41+
42+
reInitializeRegisters();
43+
return result;
44+
}
45+
46+
private void reInitializeRegisters() {
47+
incrementFrameCounter();
48+
registers.forEach( lfsr -> lfsr.initialize( sessionKey, frameCounter ) );
49+
}
50+
51+
private void incrementFrameCounter() {
52+
Utils.increment( frameCounter, FRAME_COUNTER_LENGTH );
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.thealgorithms.ciphers.a5;
2+
3+
import java.util.BitSet;
4+
5+
public interface BaseLFSR {
6+
void initialize(BitSet sessionKey, BitSet frameCounter);
7+
boolean clock();
8+
int SESSION_KEY_LENGTH = 64;
9+
int FRAME_COUNTER_LENGTH = 22;
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.thealgorithms.ciphers.a5;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Map;
6+
import java.util.TreeMap;
7+
8+
public abstract class CompositeLFSR implements BaseLFSR {
9+
protected final List<LFSR> registers = new ArrayList<>();
10+
11+
/**
12+
* Implements irregular clocking using the clock bit for each register
13+
* @return the registers discarded bit xored value
14+
*/
15+
@Override
16+
public boolean clock() {
17+
boolean majorityBit = getMajorityBit();
18+
boolean result = false;
19+
for ( var register : registers ) {
20+
result ^= register.getLastBit();
21+
if ( register.getClockBit() == majorityBit )
22+
register.clock();
23+
}
24+
return result;
25+
}
26+
27+
private boolean getMajorityBit() {
28+
Map<Boolean, Integer> bitCount = new TreeMap<>();
29+
bitCount.put( false, 0 );
30+
bitCount.put( true, 0 );
31+
32+
registers.forEach( lfsr -> bitCount.put( lfsr.getClockBit(), bitCount.get( lfsr.getClockBit() ) + 1 ) );
33+
return bitCount.get( false ) <= bitCount.get( true );
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.thealgorithms.ciphers.a5;
2+
3+
import java.util.BitSet;
4+
5+
public class LFSR implements BaseLFSR {
6+
private final BitSet register;
7+
private final int length;
8+
private final int clockBitIndex;
9+
private final int[] tappingBitsIndices;
10+
11+
public LFSR( int length, int clockBitIndex, int[] tappingBitsIndices ) {
12+
this.length = length;
13+
this.clockBitIndex = clockBitIndex;
14+
this.tappingBitsIndices = tappingBitsIndices;
15+
register = new BitSet( length );
16+
}
17+
18+
@Override
19+
public void initialize( BitSet sessionKey, BitSet frameCounter ) {
20+
register.clear();
21+
clock( sessionKey, SESSION_KEY_LENGTH );
22+
clock( frameCounter, FRAME_COUNTER_LENGTH );
23+
}
24+
25+
private void clock( BitSet key, int keyLength ) {
26+
// We start from reverse because LFSR 0 index is the left most bit
27+
// while key 0 index is right most bit, so we reverse it
28+
for ( int i = keyLength - 1; i >= 0; --i ) {
29+
var newBit = key.get( i ) ^ xorTappingBits();
30+
pushBit( newBit );
31+
}
32+
}
33+
34+
@Override
35+
public boolean clock() {
36+
return pushBit( xorTappingBits() );
37+
}
38+
39+
public boolean getClockBit() {
40+
return register.get( clockBitIndex );
41+
}
42+
43+
public boolean get( int bitIndex ) {
44+
return register.get( bitIndex );
45+
}
46+
47+
public boolean getLastBit() {
48+
return register.get( length - 1 );
49+
}
50+
51+
private boolean xorTappingBits() {
52+
boolean result = false;
53+
for ( int i : tappingBitsIndices ) {
54+
result ^= register.get( i );
55+
}
56+
return result;
57+
}
58+
59+
private boolean pushBit( boolean bit ) {
60+
boolean discardedBit = rightShift();
61+
register.set( 0, bit );
62+
return discardedBit;
63+
}
64+
65+
private boolean rightShift() {
66+
boolean discardedBit = get( length - 1 );
67+
for ( int i = length - 1; i > 0; --i ) {
68+
register.set( i, get( i - 1 ) );
69+
}
70+
register.set( 0, false );
71+
return discardedBit;
72+
}
73+
74+
@Override
75+
public String toString() {
76+
return register.toString();
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.thealgorithms.ciphers.a5;
2+
3+
// Source http://www.java2s.com/example/java-utility-method/bitset/increment-bitset-bits-int-size-9fd84.html
4+
//package com.java2s;
5+
//License from project: Open Source License
6+
7+
import java.util.BitSet;
8+
9+
public class Utils {
10+
public static boolean increment( BitSet bits, int size ) {
11+
int i = size - 1;
12+
while ( i >= 0 && bits.get( i ) ) {
13+
bits.set( i--, false );/*from w w w . j a v a 2s .c o m*/
14+
}
15+
if ( i < 0 ) {
16+
return false;
17+
}
18+
bits.set( i, true );
19+
return true;
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.thealgorithms.ciphers.a5;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.BitSet;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
import static org.junit.jupiter.api.Assertions.assertFalse;
9+
10+
// Basic tests for sanity check
11+
class LFSRTest {
12+
// Represents 0100 1110 0010 1111 0100 1101 0111 1100 0001 1110 1011 1000 1000 1011 0011 1010
13+
// But we start reverse way because bitset starts from most right (1010)
14+
byte[] sessionKeyBytes = { 58, (byte) 139, (byte) 184, 30, 124, 77, 47, 78 };
15+
16+
// Represents 11 1010 1011 0011 1100 1011
17+
byte[] frameCounterBytes = { (byte) 203, (byte) 179, 58 };
18+
19+
@Test
20+
void initialize() {
21+
BitSet sessionKey = BitSet.valueOf( sessionKeyBytes );
22+
BitSet frameCounter = BitSet.valueOf( frameCounterBytes );
23+
24+
BitSet expected = new BitSet( 19 );
25+
expected.set( 0 );
26+
expected.set( 1 );
27+
expected.set( 3 );
28+
expected.set( 4 );
29+
expected.set( 5 );
30+
expected.set( 7 );
31+
expected.set( 9 );
32+
expected.set( 10 );
33+
expected.set( 11 );
34+
expected.set( 12 );
35+
expected.set( 13 );
36+
expected.set( 15 );
37+
expected.set( 16 );
38+
expected.set( 17 );
39+
40+
LFSR lfsr0 = new LFSR( 19, 8, new int[]{ 13, 16, 17, 18 } );
41+
lfsr0.initialize( sessionKey, frameCounter );
42+
assertEquals( expected.toString(), lfsr0.toString() );
43+
}
44+
45+
@Test
46+
void clock() {
47+
BitSet sessionKey = BitSet.valueOf( sessionKeyBytes );
48+
BitSet frameCounter = BitSet.valueOf( frameCounterBytes );
49+
50+
LFSR lfsr0 = new LFSR( 19, 8, new int[]{ 13, 16, 17, 18 } );
51+
lfsr0.initialize( sessionKey, frameCounter );
52+
53+
BitSet expected = new BitSet( 19 );
54+
expected.set( 0 );
55+
expected.set( 1 );
56+
expected.set( 2 );
57+
expected.set( 4 );
58+
expected.set( 5 );
59+
expected.set( 6 );
60+
expected.set( 8 );
61+
expected.set( 10 );
62+
expected.set( 11 );
63+
expected.set( 12 );
64+
expected.set( 13 );
65+
expected.set( 14 );
66+
expected.set( 16 );
67+
expected.set( 17 );
68+
expected.set( 18 );
69+
70+
lfsr0.clock();
71+
assertEquals( expected.toString(), lfsr0.toString() );
72+
}
73+
74+
@Test
75+
void getClockBit() {
76+
BitSet sessionKey = BitSet.valueOf( sessionKeyBytes );
77+
BitSet frameCounter = BitSet.valueOf( frameCounterBytes );
78+
79+
LFSR lfsr0 = new LFSR( 19, 8, new int[]{ 13, 16, 17, 18 } );
80+
81+
assertFalse( lfsr0.getClockBit() );
82+
83+
lfsr0.initialize( sessionKey, frameCounter );
84+
85+
assertFalse( lfsr0.getClockBit() );
86+
}
87+
}

0 commit comments

Comments
 (0)