Created
May 20, 2025 19:44
-
-
Save sbOogway/d4e7af5b01f7e1df1769daaa0e2fbe76 to your computer and use it in GitHub Desktop.
DES implementation in scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
specifications: https://csrc.nist.gov/files/pubs/fips/46-3/final/docs/fips46-3.pdf | |
author: Mattia Papaccioli | |
github: https://github.com/sbOogway | |
email: mattiapapaccioli@gmail.com | |
pure scala implementation of des, mostly using functional programming | |
written for learning purposes not meant for production | |
defects: | |
- proper padding | |
- non printable characters handling when converting bit string to regular string | |
Copyright (c) 2025 Mattia Papaccioli | |
Permission is hereby granted, free of charge, to any person obtaining a copy of this | |
software and associated documentation files (the "Software"), to deal in the Software | |
without restriction, including without limitation the rights to use, copy, modify, merge, | |
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons | |
to whom the Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all copies | |
or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING | |
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
import scala.util.matching.Regex | |
import scala.collection.mutable.ListBuffer | |
object vars { | |
/* | |
in bytes. each character is 1 byte | |
8 bytes are 64 bits | |
*/ | |
val BLOCK_SIZE: Int = 64 | |
val KEY_SIZE: Int = 8 | |
val ROUNDS = 16 | |
val IP = List(58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7) | |
val FP = List(40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25) | |
val P = List(16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25) | |
val PC1 = List(57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4) | |
val PC2 = List(14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32) | |
val SHIFTS = List(1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1) | |
val S1 = List( | |
List(14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7), | |
List(0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8), | |
List(4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0), | |
List(15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13) | |
) | |
val S2 = List( | |
List(15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10), | |
List(3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5), | |
List(0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15), | |
List(13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9) | |
) | |
val S3 = List( | |
List(10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8), | |
List(13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1), | |
List(13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7), | |
List(1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12) | |
) | |
val S4 = List( | |
List(7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15), | |
List(13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9), | |
List(10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4), | |
List(3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14) | |
) | |
val S5 = List( | |
List(2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9), | |
List(14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6), | |
List(4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14), | |
List(11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3) | |
) | |
val S6 = List( | |
List(12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11), | |
List(10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8), | |
List(9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6), | |
List(4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13) | |
) | |
val S7 = List( | |
List(4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1), | |
List(13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6), | |
List(1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2), | |
List(6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12) | |
) | |
val S8 = List( | |
List(13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7), | |
List(1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2), | |
List(7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8), | |
List(2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11) | |
) | |
val SBOXES = List(S1, S2, S3, S4, S5, S6, S7, S8) | |
} | |
// def padding(text: String): Block = | |
// if (text.length == 0) | |
// return Nil | |
// if (text.length == vars.BLOCK_SIZE) | |
// return text.getBytes("UTF-8").toList | |
// var amount = vars.BLOCK_SIZE - text.length | |
// // println(amount) | |
// // println(amount.toByte) | |
// return text.getBytes("UTF-8").toList ++ List.fill(amount)(amount.toHexString.toByte) | |
// def prepare_plaintext(text: String, buffer: List[Block]): Plaintext = | |
// if (text.length == 0) | |
// return buffer | |
// var end = math.min(text.length, vars.BLOCK_SIZE) | |
// println(s"dbg $end") | |
// return padding(text.slice(0, end)) :: prepare_plaintext(text.slice(end, text.length), buffer) | |
def check_bit_string(s: String): Unit = | |
require("^[01]+$".r.findFirstIn(s).getOrElse("") != "", "invalid bit string") | |
def to_binary(i: Int, digits: Int = 8) = | |
String.format("%" + digits + "s", i.toBinaryString).replace(' ', '0') | |
def string_to_bits(s: String): String = | |
if (s.size == 0) | |
return "" | |
return to_binary(s.head.toChar) + string_to_bits(s.tail) | |
def permutation(original: String, bits: String, indexes: List[Int]): String = | |
if (indexes == Nil) | |
return bits | |
return permutation(original, bits + original(indexes.head - 1), indexes.tail) | |
def divide(s: String): (String, String) = | |
return (s.slice(0, s.size / 2), s.slice(s.size / 2, s.size)) | |
def count_ones(s: String): Int = | |
if (s == "") | |
return 0 | |
if (s.head == '1') | |
return 1 + count_ones(s.tail) | |
return count_ones(s.tail) | |
def expansion(s: String): String = | |
require(s.length == 32, "block wrong length for expansion") | |
val slide = s.sliding(4, 4).toList | |
var r = "" | |
/* this is equivalent to | |
return ( | |
s"${slide(7)(3)}${slide(0)}${slide(1)(0)}" + | |
s"${slide(0)(3)}${slide(1)}${slide(2)(0)}" + | |
s"${slide(1)(3)}${slide(2)}${slide(3)(0)}" + | |
s"${slide(2)(3)}${slide(3)}${slide(4)(0)}" + | |
s"${slide(3)(3)}${slide(4)}${slide(5)(0)}" + | |
s"${slide(4)(3)}${slide(5)}${slide(6)(0)}" + | |
s"${slide(5)(3)}${slide(6)}${slide(7)(0)}" + | |
s"${slide(6)(3)}${slide(7)}${slide(0)(0)}" | |
) | |
*/ | |
for i <- 0 to 7 do r += s"${slide((7 + i) % 8)(3)}${slide(i)}${slide((1 + i) % 8)(0)}" | |
require(r.length == 48) | |
return r | |
def shift(s: String, count: Int): String = | |
if (count == 0) | |
return s | |
return shift(s"${s.tail}${s.head}", count - 1) | |
// debugged against other implementation is good | |
def key_schedule(key: String): ListBuffer[String] = | |
check_bit_string(key) | |
val pc1 = permutation(key, "", vars.PC1) | |
require(pc1.length == 56, "error key scheduling PC1") | |
var (l, r) = divide(pc1) | |
var keys: ListBuffer[String] = ListBuffer() | |
for i <- 0 to vars.ROUNDS - 1 do | |
l = shift(l, vars.SHIFTS(i)) | |
r = shift(r, vars.SHIFTS(i)) | |
// r <<= vars.SHIFTS(i) | |
keys += permutation(s"${l}${r}", "", vars.PC2) | |
return keys | |
def sbox(block: String): String = | |
check_bit_string(block) | |
require(block.size == 48, "wrong block size in input for sbox") | |
val slide = block.sliding(6, 6).toList | |
var r = "" | |
for i <- 0 to 7 do | |
var row = Integer.parseInt(s"${slide(i)(0)}${slide(i)(5)}", 2) | |
var column = Integer.parseInt(slide(i).tail.slice(0, 4), 2) | |
r += to_binary(vars.SBOXES(i)(row)(column), 4) | |
require(r.size == 32, "somethings wrong in the sbox code bro") | |
return r | |
def f(block: String, key: String): String = | |
check_bit_string(block) | |
check_bit_string(key) | |
val e = expansion(block) | |
val in_sbox = xor(e, key) | |
val in_p = sbox(in_sbox) | |
return permutation(in_p, "", vars.P) | |
def des(plain: String, key: String, mode: String = "encrypt"): String = | |
require(plain.size % 8 == 0, "wrong plain text length. pad that shit up") | |
require(key.length == 8, "wrong key length.") | |
var plain_bits = string_to_bits(plain) | |
var key_bits = string_to_bits(key) | |
var keys = key_schedule(key_bits) | |
if (mode == "decrypt") | |
keys = keys.reverse | |
plain_bits = permutation(plain_bits, "", vars.IP) | |
var (l, r) = divide(plain_bits) | |
var tmp: String = "" | |
for i <- 0 to vars.ROUNDS - 1 do | |
tmp = r | |
val f_out = f(r, keys(i)) | |
r = xor(l, f_out) | |
l = tmp | |
val out = permutation(s"$r$l", "", vars.FP) | |
return bit_string_to_string(out) | |
def bit_string_to_string(s: String): String = | |
if (s.size == 0) | |
return "" | |
check_bit_string(s) | |
require(s.size % 8 == 0, "wrong bit string length") | |
val char = Integer.parseInt(s.slice(0, 8), 2) | |
return s"${Integer.parseInt(s.slice(0, 8), 2).toChar}${bit_string_to_string(s.slice(8, s.size))}" | |
def xor(s1: String, s2: String): String = | |
if (s1.size == 0) | |
return "" | |
require(s1.size == s2.size) | |
return (s1.head.toInt ^ s2.head.toInt).toString + xor(s1.tail, s2.tail) | |
@main | |
def main(): Unit = | |
val text = "testosec" | |
val key = "abcdefgh" | |
var cipher = des(text, key) | |
println(cipher) | |
var plain = des(cipher, key, "decrypt") | |
println(plain) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment