Skip to content

Implement A5 Cipher #3292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.thealgorithms.ciphers.a5.sound;

import com.thealgorithms.ciphers.a5.A5Cipher;

import javax.sound.sampled.TargetDataLine;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.BitSet;

public class A5SoundRecorder extends SoundRecorder {
private final BitSet sessionKey = BitSet.valueOf( new byte[]{ 58, (byte) 139, (byte) 184, 30, 62, 77, 47, 78 } );
private final BitSet frameCounter = BitSet.valueOf( new byte[]{ (byte) 203, (byte) 179, 58 } );
private final A5Cipher a5 = new A5Cipher( sessionKey, frameCounter );
private static final int STREAM_ENCRYPTION_SIZE_BYTES = 28; // we won't encrypt that 0.5 byte for convenience now
private static final int STREAM_ENCRYPTION_SIZE_BITS = 228;

@Override
public void buildByteOutputStream( final ByteArrayOutputStream out, final TargetDataLine line, int frameSizeInBytes, final int bufferLengthInBytes ) throws IOException {
final byte[] data = new byte[ bufferLengthInBytes ];
int numBytesRead;

line.start();
while ( thread != null ) {
if ( ( numBytesRead = line.read( data, 0, bufferLengthInBytes ) ) == -1 ) {
break;
}
encryptBytes( out, data, numBytesRead );
}
}

private void encryptBytes( final ByteArrayOutputStream out, byte[] data, int numBytesRead ) {
int paddedBytesSize = numBytesRead + ( STREAM_ENCRYPTION_SIZE_BYTES - ( numBytesRead % STREAM_ENCRYPTION_SIZE_BYTES ) );
var paddedData = Arrays.copyOf( data, paddedBytesSize );

for ( int i = 0; i < paddedData.length; i += STREAM_ENCRYPTION_SIZE_BYTES ) {
var currentBytes = Arrays.copyOfRange( paddedData, i, i + STREAM_ENCRYPTION_SIZE_BYTES );
var currentBits = BitSet.valueOf( currentBytes );
var encryptedBits = a5.encrypt( currentBits );
var currentEncryptedBytes= Arrays.copyOf( encryptedBits.toByteArray(), STREAM_ENCRYPTION_SIZE_BYTES );

out.write( currentEncryptedBytes, 0, currentEncryptedBytes.length );
}
}

public void decryptLastRecording() {
byte[] data = this.getByteAudio();
a5.resetCounter();

try ( final ByteArrayOutputStream out = new ByteArrayOutputStream() ) {
var format = getFormat();
int frameSizeInBytes = format.getFrameSize();
encryptBytes( out, data, data.length );
setAudioInputStream( convertToAudioIStream( out, frameSizeInBytes ) );
} catch ( Exception ex ) {
ex.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.thealgorithms.ciphers.a5.sound;

import javax.sound.sampled.AudioFormat;

public class ApplicationProperties {
public final AudioFormat.Encoding ENCODING = AudioFormat.Encoding.PCM_SIGNED;
public final float RATE = 44100.0f;
public final int CHANNELS = 1;
public final int SAMPLE_SIZE = 16;
public final boolean BIG_ENDIAN = true;
}
Loading