From e4eb99ff189e6ec9db1640fbca1b875d36daa9b0 Mon Sep 17 00:00:00 2001 From: J2mF <37801310+HHHMHA@users.noreply.github.com> Date: Tue, 27 Sep 2022 19:33:13 +0300 Subject: [PATCH] Add A5 Cipher (#3292) --- .../thealgorithms/ciphers/a5/A5Cipher.java | 30 +++++++ .../ciphers/a5/A5KeyStreamGenerator.java | 54 ++++++++++++ .../thealgorithms/ciphers/a5/BaseLFSR.java | 10 +++ .../ciphers/a5/CompositeLFSR.java | 35 ++++++++ .../com/thealgorithms/ciphers/a5/LFSR.java | 78 +++++++++++++++++ .../com/thealgorithms/ciphers/a5/Utils.java | 21 +++++ .../thealgorithms/ciphers/a5/LFSRTest.java | 87 +++++++++++++++++++ 7 files changed, 315 insertions(+) create mode 100644 src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java create mode 100644 src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java create mode 100644 src/main/java/com/thealgorithms/ciphers/a5/BaseLFSR.java create mode 100644 src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java create mode 100644 src/main/java/com/thealgorithms/ciphers/a5/LFSR.java create mode 100644 src/main/java/com/thealgorithms/ciphers/a5/Utils.java create mode 100644 src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java diff --git a/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java b/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java new file mode 100644 index 000000000..01a3e665f --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java @@ -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(); + } + +} diff --git a/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java b/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java new file mode 100644 index 000000000..4232c66fd --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java @@ -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 ); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/a5/BaseLFSR.java b/src/main/java/com/thealgorithms/ciphers/a5/BaseLFSR.java new file mode 100644 index 000000000..18ad91378 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/a5/BaseLFSR.java @@ -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; +} diff --git a/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java b/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java new file mode 100644 index 000000000..e830f5dbb --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java @@ -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 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 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 ); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/a5/LFSR.java b/src/main/java/com/thealgorithms/ciphers/a5/LFSR.java new file mode 100644 index 000000000..3fdf08cbb --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/a5/LFSR.java @@ -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(); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/a5/Utils.java b/src/main/java/com/thealgorithms/ciphers/a5/Utils.java new file mode 100644 index 000000000..96bf0fc1e --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/a5/Utils.java @@ -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; + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java b/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java new file mode 100644 index 000000000..5c1d0314a --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java @@ -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() ); + } +}