Skip to content

Commit

Permalink
Add A5 Cipher (TheAlgorithms#3292)
Browse files Browse the repository at this point in the history
  • Loading branch information
HHHMHA authored Sep 27, 2022
1 parent 8ca571d commit e4eb99f
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 0 deletions.
30 changes: 30 additions & 0 deletions src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.thealgorithms.ciphers.a5;

import java.util.BitSet;

// https://en.wikipedia.org/wiki/A5/1
public class A5Cipher {
private final A5KeyStreamGenerator keyStreamGenerator;
private static final int KEY_STREAM_LENGTH = 228; // 28.5 bytes so we need to pad bytes or something

public A5Cipher( BitSet sessionKey, BitSet frameCounter ) {
keyStreamGenerator = new A5KeyStreamGenerator();
keyStreamGenerator.initialize( sessionKey, frameCounter );
}

public BitSet encrypt( BitSet plainTextBits ) {
// create a copy
var result = new BitSet( KEY_STREAM_LENGTH );
result.xor( plainTextBits );

var key = keyStreamGenerator.getNextKeyStream();
result.xor( key );

return result;
}

public void resetCounter() {
keyStreamGenerator.reInitialize();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.thealgorithms.ciphers.a5;

import java.util.BitSet;

// TODO: raise exceptions for improper use
public class A5KeyStreamGenerator extends CompositeLFSR {
private BitSet initialFrameCounter;
private BitSet frameCounter;
private BitSet sessionKey;
private static final int INITIAL_CLOCKING_CYCLES = 100;
private static final int KEY_STREAM_LENGTH = 228; // 28.5 bytes so we need to pad bytes or something

@Override
public void initialize( BitSet sessionKey, BitSet frameCounter ) {
this.sessionKey = sessionKey;
this.frameCounter = (BitSet) frameCounter.clone();
this.initialFrameCounter = (BitSet) frameCounter.clone();
registers.clear();
LFSR lfsr1 = new LFSR( 19, 8, new int[]{ 13, 16, 17, 18 } );
LFSR lfsr2 = new LFSR( 22, 10, new int[]{ 20, 21 } );
LFSR lfsr3 = new LFSR( 23, 10, new int[]{ 7, 20, 21, 22 } );
registers.add( lfsr1 );
registers.add( lfsr2 );
registers.add( lfsr3 );
registers.forEach( lfsr -> lfsr.initialize( sessionKey, frameCounter ) );
}

public void reInitialize() {
this.initialize( sessionKey, initialFrameCounter );
}

public BitSet getNextKeyStream() {
for ( int cycle = 1; cycle <= INITIAL_CLOCKING_CYCLES; ++cycle )
this.clock();

BitSet result = new BitSet( KEY_STREAM_LENGTH );
for ( int cycle = 1; cycle <= KEY_STREAM_LENGTH; ++cycle ) {
boolean outputBit = this.clock();
result.set( cycle - 1, outputBit );
}

reInitializeRegisters();
return result;
}

private void reInitializeRegisters() {
incrementFrameCounter();
registers.forEach( lfsr -> lfsr.initialize( sessionKey, frameCounter ) );
}

private void incrementFrameCounter() {
Utils.increment( frameCounter, FRAME_COUNTER_LENGTH );
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/thealgorithms/ciphers/a5/BaseLFSR.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.thealgorithms.ciphers.a5;

import java.util.BitSet;

public interface BaseLFSR {
void initialize(BitSet sessionKey, BitSet frameCounter);
boolean clock();
int SESSION_KEY_LENGTH = 64;
int FRAME_COUNTER_LENGTH = 22;
}
35 changes: 35 additions & 0 deletions src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.thealgorithms.ciphers.a5;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public abstract class CompositeLFSR implements BaseLFSR {
protected final List<LFSR> registers = new ArrayList<>();

/**
* Implements irregular clocking using the clock bit for each register
* @return the registers discarded bit xored value
*/
@Override
public boolean clock() {
boolean majorityBit = getMajorityBit();
boolean result = false;
for ( var register : registers ) {
result ^= register.getLastBit();
if ( register.getClockBit() == majorityBit )
register.clock();
}
return result;
}

private boolean getMajorityBit() {
Map<Boolean, Integer> bitCount = new TreeMap<>();
bitCount.put( false, 0 );
bitCount.put( true, 0 );

registers.forEach( lfsr -> bitCount.put( lfsr.getClockBit(), bitCount.get( lfsr.getClockBit() ) + 1 ) );
return bitCount.get( false ) <= bitCount.get( true );
}
}
78 changes: 78 additions & 0 deletions src/main/java/com/thealgorithms/ciphers/a5/LFSR.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.thealgorithms.ciphers.a5;

import java.util.BitSet;

public class LFSR implements BaseLFSR {
private final BitSet register;
private final int length;
private final int clockBitIndex;
private final int[] tappingBitsIndices;

public LFSR( int length, int clockBitIndex, int[] tappingBitsIndices ) {
this.length = length;
this.clockBitIndex = clockBitIndex;
this.tappingBitsIndices = tappingBitsIndices;
register = new BitSet( length );
}

@Override
public void initialize( BitSet sessionKey, BitSet frameCounter ) {
register.clear();
clock( sessionKey, SESSION_KEY_LENGTH );
clock( frameCounter, FRAME_COUNTER_LENGTH );
}

private void clock( BitSet key, int keyLength ) {
// We start from reverse because LFSR 0 index is the left most bit
// while key 0 index is right most bit, so we reverse it
for ( int i = keyLength - 1; i >= 0; --i ) {
var newBit = key.get( i ) ^ xorTappingBits();
pushBit( newBit );
}
}

@Override
public boolean clock() {
return pushBit( xorTappingBits() );
}

public boolean getClockBit() {
return register.get( clockBitIndex );
}

public boolean get( int bitIndex ) {
return register.get( bitIndex );
}

public boolean getLastBit() {
return register.get( length - 1 );
}

private boolean xorTappingBits() {
boolean result = false;
for ( int i : tappingBitsIndices ) {
result ^= register.get( i );
}
return result;
}

private boolean pushBit( boolean bit ) {
boolean discardedBit = rightShift();
register.set( 0, bit );
return discardedBit;
}

private boolean rightShift() {
boolean discardedBit = get( length - 1 );
for ( int i = length - 1; i > 0; --i ) {
register.set( i, get( i - 1 ) );
}
register.set( 0, false );
return discardedBit;
}

@Override
public String toString() {
return register.toString();
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/thealgorithms/ciphers/a5/Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.thealgorithms.ciphers.a5;

// Source http://www.java2s.com/example/java-utility-method/bitset/increment-bitset-bits-int-size-9fd84.html
//package com.java2s;
//License from project: Open Source License

import java.util.BitSet;

public class Utils {
public static boolean increment( BitSet bits, int size ) {
int i = size - 1;
while ( i >= 0 && bits.get( i ) ) {
bits.set( i--, false );/*from w w w . j a v a 2s .c o m*/
}
if ( i < 0 ) {
return false;
}
bits.set( i, true );
return true;
}
}
87 changes: 87 additions & 0 deletions src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.thealgorithms.ciphers.a5;

import org.junit.jupiter.api.Test;

import java.util.BitSet;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

// Basic tests for sanity check
class LFSRTest {
// Represents 0100 1110 0010 1111 0100 1101 0111 1100 0001 1110 1011 1000 1000 1011 0011 1010
// But we start reverse way because bitset starts from most right (1010)
byte[] sessionKeyBytes = { 58, (byte) 139, (byte) 184, 30, 124, 77, 47, 78 };

// Represents 11 1010 1011 0011 1100 1011
byte[] frameCounterBytes = { (byte) 203, (byte) 179, 58 };

@Test
void initialize() {
BitSet sessionKey = BitSet.valueOf( sessionKeyBytes );
BitSet frameCounter = BitSet.valueOf( frameCounterBytes );

BitSet expected = new BitSet( 19 );
expected.set( 0 );
expected.set( 1 );
expected.set( 3 );
expected.set( 4 );
expected.set( 5 );
expected.set( 7 );
expected.set( 9 );
expected.set( 10 );
expected.set( 11 );
expected.set( 12 );
expected.set( 13 );
expected.set( 15 );
expected.set( 16 );
expected.set( 17 );

LFSR lfsr0 = new LFSR( 19, 8, new int[]{ 13, 16, 17, 18 } );
lfsr0.initialize( sessionKey, frameCounter );
assertEquals( expected.toString(), lfsr0.toString() );
}

@Test
void clock() {
BitSet sessionKey = BitSet.valueOf( sessionKeyBytes );
BitSet frameCounter = BitSet.valueOf( frameCounterBytes );

LFSR lfsr0 = new LFSR( 19, 8, new int[]{ 13, 16, 17, 18 } );
lfsr0.initialize( sessionKey, frameCounter );

BitSet expected = new BitSet( 19 );
expected.set( 0 );
expected.set( 1 );
expected.set( 2 );
expected.set( 4 );
expected.set( 5 );
expected.set( 6 );
expected.set( 8 );
expected.set( 10 );
expected.set( 11 );
expected.set( 12 );
expected.set( 13 );
expected.set( 14 );
expected.set( 16 );
expected.set( 17 );
expected.set( 18 );

lfsr0.clock();
assertEquals( expected.toString(), lfsr0.toString() );
}

@Test
void getClockBit() {
BitSet sessionKey = BitSet.valueOf( sessionKeyBytes );
BitSet frameCounter = BitSet.valueOf( frameCounterBytes );

LFSR lfsr0 = new LFSR( 19, 8, new int[]{ 13, 16, 17, 18 } );

assertFalse( lfsr0.getClockBit() );

lfsr0.initialize( sessionKey, frameCounter );

assertFalse( lfsr0.getClockBit() );
}
}

0 comments on commit e4eb99f

Please sign in to comment.