Affine cipher
In this problem we consider an affine cipher, which is a special case of a monoalphabetic substitution cipher, where each letter in an alphabet is mapped to its numeric equivalent, encrypted with a simple mathematical function using modular artihmetic.
Problem definition
The encryption function has the form
\[E(x) = (Ax + B) \mod M,\]
and converted back to a letter. Here $A$ and $B$ are the encryption keys and $M$ is the size of the alphabet. In this problem we consider an affine cipher with $M = 256$, and encryption keys $A = 15$ and $B=27$. Decryption is always possible provided that $A$ and $M$ are co-prime (i.e. they have only 1 as their common factor), and the decryption function is:
\[D(x) = A^{-1}(x - B) \mod M,\]
where $A^{-1}$ is the multiplicative inverse of $A$ modulo $M$, i.e. satisfies the equation $1 = AA^{-1} \mod M$.
Since each letter can be encrypted and decrypted independently, we can use the GPU to decrypt a certain text in parallel.
Serial implementation (CPU)
Encryption function
The following C function is used to compute the module between two integers $a$ and $b$, guaranteeting that the result is always nonnegative.
__device__ int modulo(int a, int b){
int r = a % b;
r = (r < 0) ? r + b : r;
return r;
}
The implementation in Julia is:
function _mod(a, b)
r = a % b
r = (r < 0) ? r + b : r
return r
end
Decryption function
function decrypt(message::Vector{<:Integer}, Ainv, B, M)
n = length(message)
out = similar(message)
for i in 1:n
out[i] = decrypt(message[i], Ainv, B, M)
end
return out
end
function decrypt(x::Integer, Ainv, B, M)
y = Ainv * (x - B)
return _mod(y, M)
end
For instance,
message = [1, 2, 3]
julia> A = 15;
julia> Ainv = -17 # inverse of A modulo M
julia> B = 27
julia> M = 256;
julia> decrypt(message, A, B, M)
3-element Vector{Int64}:
122
137
152
Data
data = "secreto.txt"
function _open(path)
file = open(path, "r")
text = Vector{UInt8}()
while !eof(file)
i = read(file, UInt8)
push!(text, i)
end
close(file)
return text
end
Benchmark
using StringEncodings, BenchmarkTools
text = _open(data)
@btime text = _open($data)
text_dec = decrypt(text, Ainv, B, M);
@btime decrypt($text, $Ainv, $B, $M);
result = decode(text_dec, "UTF-8")
@btime decode($text_dec, "UTF-8");
84.849 ms (34 allocations: 2.00 MiB)
8.027 ms (2 allocations: 825.70 KiB)
8.538 ms (38083 allocations: 4.29 MiB)
Result
Cien años de soledad
EDITADO POR "EDICIONES LA CUEVA"
println(result[1:1000])
Multi-threaded implementation (CPU)
julia> Threads.nthreads()
4
function decrypt_threaded(message::Vector{<:Integer}, Ainv, B, M)
n = length(message)
out = similar(message)
Threads.@threads for i in 1:n
out[i] = decrypt(message[i], Ainv, B, M)
end
return out
end
function decrypt(x::Integer, Ainv, B, M)
y = Ainv * (x - B)
return _mod(y, M)
end
Using CuArrays (GPU)
Writing the CUDA kernel
Multiple-block approach
In this part extend the algorithm to work with multiple blocks of threads such that it can process texts of arbitrary length.