Skip to content

Instantly share code, notes, and snippets.

@sbOogway
Created May 20, 2025 19:44
Show Gist options
  • Save sbOogway/d4e7af5b01f7e1df1769daaa0e2fbe76 to your computer and use it in GitHub Desktop.
Save sbOogway/d4e7af5b01f7e1df1769daaa0e2fbe76 to your computer and use it in GitHub Desktop.
DES implementation in scala
/*
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