#! /bin/bash

# kcaes.sh V1.4, KCAES2C.kcc und KCAES6C.kcc V1.4
# Verschlüsseln und entschlüsseln im KC85/2-5 oder auf einen linuxbasierten PC austauschbare Dateien.
#
# Inhalt: KCAES2C.kcc, KCAES6C.kcc für die Adressen 2C00H und 6C00H für KC85 mit 16 oder 32 KByte Ram Speicher und dieses Script.
#
# Die Programme KCAESxC.kcc für den KC85/2-5 verschlüsseln nach dem anerkannten AES Algorithmus. Die Crypt-Dateien können mit Hilfe des Linux-
# Scripts kcaes.sh auch am PC mit Hilfe der OpenSSL-Bibliothek ver- und entschlüsselt werden. Bei Ausführung von Scripten in Linux muss das
# Execute Bit gesetzt sein. Bei der Verschlüsselung im Linux-Script wird der KC-Head für direktem Load in ein KC erstellt. Eine Verschlüsselung
# im KC hat den Vorteil, dass in einem Singeltasksystem mit festem ROM keine Trojaner, Keylogger oder sonstige Programme im Hintergrund ihr
# Unwesen treiben können. Der 128-256 Key für die Verschlüsselung in AES wird vorab nach 24 Runden aus dem SHA256-Hash des Passworts, dem Salt
# und einer laufenden Nummer erzeugt. Zusätzliche rechenintensive Verfahren wie z.B. scrypt oder bcrypt werden hier nicht eingesetzt. Sie dienen
# nur dazu auch bei kurzen Passwörtern eine gewisse Sicherheit zu gewährleisten. Daher sind ausreichend lange Passwörter hier Voraussetzung für
# eine sichere Verschlüsselung. Bei der Passworteingabe kann mit der Cursor-UP Taste die Klarsichteingabe aktiviert werden. Es ist in diesem Fall
# nur eine einmalige Eingabe des Passwortes notwendig, sonst mit Wiederholung. Bei der Passworteingabe ist ohne Shift-Taste Kleinschreibung
# eingestellt. Zu beachten ist, dass bei der Entschlüsselung über den Crypt-Speicherbereich 'on the fly' geschrieben wird. Bei falschem Passwort kann
# der verschlüsselte Bereich nicht erneut entschlüsselt werden, da er zerstört ist. Die Crypt-Datei muss erneut geladen werden. Nur so kann auch
# fast der gesamte RAM ver- und entschlüsselt werden. Der Variablenspeicher wird vor Beendigung des Programms zur Sicherheit gelöscht. Es können
# mehrere Bereiche nacheinander erst verschlüsselt und anschließend gespeichert werden. Beim Verschlüsseln erscheint ein Warnhinweis, wenn
# bereits eine oder mehrere AES-Chiffre im Speicher ab 200H vorhanden sind. Durch den Aufruf von 'DECAES' ohne Startparameter wird der Speicher
# ebenfalls ab 200H nach einem verschlüsselten Speicherblock durchsucht. Wenn einer gefunden wird, kann er durch Eingabe des Passwortes ent-
# schlüsselt oder mit BRK der Vorgang abgebrochen werden. Nach der Ver- und Entschlüsselung werden der Speicheranfang und -ende im KC85 angezeigt
# um mit dem 'SAVE'-Kommando den Bereich speichern zu können.
#
# Sonderzeichen:
# Die ausschließliche Nutzung im KC für ver- und entschlüsselte Programme mit Sonderzeichen ist ohne Einschränkungen möglich. Wenn aber im Austausch
# mit Hilfe des Script und KC gearbeitet werden soll, ist aufgrund einiger Unterschiede in der ASCII Zuordnung der Systeme Folgendes zu beachten:
#
# Sonderzeichen mit identischer ASCII Zuordnung für Nutzung mit verschiedenen Systemen:
# ! "$%&()=*+#';,:._-?<>/@^
#
# Unterschiedliche ASCII Zuordnung, die eine Beachtung im Passwort erfordern:
#  KC PC         ASCII
#  █ = [          91
#  | = \          92
#  ¬ = ]          93
#  © = `          96
#  ä = {         123
#  ö = |         124
#  ü = }         125
#  ß = ~         126
#    = §,€,.. >= 127 
#
# Obwohl der KC85/2 nur Großbuchstaben anzeigt, funktioniert es auch mit Kleinbuchstaben, da der richtige ASCII-Code übergeben wird.
# Es sollte vorher getestet werden.    
#
# Mit der Hashfunktion, die für die Erstellung des Keys für AES benötigt wird, können auch z.B. Prüfsummen für Rom-Module zur Fehlererkennung
# erstellt werden. Ein verändertes Bit erzeugt einen vollkommenen anderen Hash. Wichtig ist die korrekte Eingabe von Start und Ende.   

#
# Zur Beachtung:
# Die Benutzung der Programme ist auf eine Gefahr!
# Wenn das Passwort vergessen wird, ist eine Entschlüsselung nicht mehr möglich.

#  --- kcaes.sh ---
# Encrypt: kcaes.sh -e -in -out [-kcload Adresse] [-128/-192/-256] [-Mode] Beispiel: kcaes.sh -e -in text.txt -out crypt.kcc -kcload 2A0 -192 -CBC
# Decrypt: kcaes.sh [-d] -in -out                                          Beispiel: kcaes.sh -d -in crypt.kcc -out text.txt

# --- KCAES.kcc ---
# Encrypt: AESX... Startadresse Endadresse [128/192/256]                   Beispiel: AESXCTR 1000 1200 256
# Decrypt: DECAES  [Startadresse]                                          Beispiel: DECAES
# Hash:    SHA256  Startadresse Endadresse                                 Beispiel: SHA256  E000 FFFF                                   
#
# Start- und Endadressen definieren den korrekten Speicherbereich im RAM. Die Endadresse schließt das letzte Byte mit ein. Bei den KCs wird beim
# SAVE-Kommando leider nur die angegebene Endadresse-1 gespeichert. Das letzte Byte fehlt, was bei verschlüsselten Dateien nach Einlesen ein Fehler
# beim Entschlüsseln erzeugt. Ab der Version 1.3 von KCAES wird als dritten Parameter beim Verschlüsseln der Endwert für das SAVE-Kommando mit angezeigt.
# Muster: ENC AES128 [RAM/Save-Start] [RAM-End] [Save-End] Von z.B. JKCEMU erfolgt das Speichern in einer Datei aus dem Menü heraus korrekt. Das Byte
# vom Endwert wird mit gespeichert, so das der zweite Parameter als Endwert genommen werden kann.      
#       
# Verschlüsselter Block
# 0-1   Kennung "IV"
# 2-17  16 Bytes IV-Block
# 18    Kennung "S" für Salt
# 19-26 8 Bytes Salt
# 27-28 Länge Crypt-Block
# 29-31 Art der Verschlüsselung
#       z.B. CB8 =CB(C),(12)8 / CT6 =CT(R),(25)6 / CF2 =CF(B),(19)2
# 32-.. Crypt-Daten
#
# Errorcodes ab V1.4
# 1 = zu wenig Parameter
# 2 = Endadresse kleiner o. gleich Startadresse
# 3 = Daten- oder Passwortfehler 
# 4 = keine verschlüsselten Daten gefunden
# 5 = Endadresse in oder hinter Programmbereich

# Ab V1.4 kann KCAES von anderen Programmen aufgerufen und somit automatisiert werden. KCAES gibt nach Ausführung fogende Werte zurück:
# CY=1 Fehler oder Break, A=Error-Fehlercode, A=0 Break
# CY=0 OK, A=Anzahl der Werte, HL,DE,BC entsprechend belegt (bei SHA256 HL=Adresse Hash im RAM, Länge 32 Byte)

# Beispiel                ;Parameterübergabe für KCAES erfolgt über Argumente ARGN-ARGx
#         LD (ARGn),...   ;analog des Menüaufrufes z.B. ARGN=3, ARG1=1000, ARG2=1500, ARG3=256
#         LD DE,CTR
#         CALL AES
#         JR C,Abbruch	  ;Carry-Flag? (Rücksprung von KCAES erfolgt hierher)
#         ...             ;ab hier Code bei erfolgreicher Ausführung z.B. Save AES-Chiffre
#         RET
#
# CTR     DEFM 'AESXCTR',0
#
# AES     LD A,07FH       ;Prolog
#         LD HL,2C00H	  ;RAM nach Menükommandos von KCAES2C o. KCAES6C durchsuchen
#         LD BC,6CFFH-2C00H
#         CALL 0F003H     ;UP-Nr.: 1DH
#         DEFB 1DH        ;ZSUCH
#         JR NC,noAES     ;Carry-Flag?
#         INC HL          ;Epilog-Byte überspringen
#         JP (HL)         ;JP KCAES, RET erfolgt von KCAES
# noAES   POP HL          ;Rücksprung CALL AES vom Stack nehmen!
#         ...             ;ab hier Code wenn Kommando von KCAES nicht gefunden wurde
#         RET
# Abbruch ...             ;ab hier Code für Error und Break
#         RET

# Änderungen:
# kcaes.sh V1.1
# Die Erkennung einer KC-Crypt-Datei wurde verbessert
#
# KCAESxC.kcc V1.1
# Änderung der Menüeinträge ist notwendig (Entfernung Sonderzeichen), da sie in einigen KC-CAOS Versionen nicht angezeigt wurden
#
# kcaes.sh V1.2
# Beschreibung ergänzt
#
# KCAESxC.kcc V1.2
# Überarbeitung der Anzeige von Hash und Passwortabfrage und Korrektur bei Umschaltung zur Klarsichtanzeige im Wiederholungsmodus auf Ersteingabe. 
# 
# kcaes.sh V1.3
# Beschreibung ergänzt
#
# KCAESxC.kcc V1.3
# Die Ausgabe der Verschlüsselung zeigt jetzt einen dritten Parameter an. Um den verschlüsselten Speicherbereich korrekt zu speichern, ist der
# dritte Parameter die Endadresse für das SAVE-Kommando vom KC. Bei JKCEMU ist der zweite Parameter ausreichend, da die Speicherung vom Dateimenü
# richtig erfolgt.
#
# kcaes.sh V1.4
# Beschreibung ergänzt
#
# KCAESxC.kcc V1.4
# Sichtbare Speicherzugriffe bei Passworteingaben am KC85/2,3 wurden beseitigt. Außerdem gibt KCAES jetzt 5 verschiedene Errorcodes aus, um die
# Fehler besser eingrenzen zu können. KCAES kann jetzt durch andere Programme mit Parameterübergabe aufgerufen werden. Wenn beim Verschlüsseln
# ab 200H bereits ein chiffriter Speicherbereich vorhanden ist, erfolgt jetzt ein Warnhinweis. Weiterhin wurden einige Codeoptimierungen durchgeführt.
# 


# Script zum Ver- und Entschlüsseln kompatibel mit KCAES für austauschbare Dateien mit dem KC

#Wandelt in 16-Bit Hexzahl als String mit Tausch LoHi um
function hex16() { local hex=$1; hex=000$(printf '%x' $hex); hex=${hex: -4}; echo ${hex: -2}${hex:0:2}; }

if (( $# > 1 )); then
  arg=$*
  while (( "$#" )); do [ "$1" = "-in" ] && in="$2"; [ "$1" = "-out" ] && out="$2"; shift; done
  if [ "$in" != "" ] && [ -f "${in}" ] && (( ${#out} > 1 )); then
    aes=$(echo -n ${arg} | grep -oP '(?<=-)(192|256)(?=\s|$)'); if [[ $aes = "" ]]; then aes=128; fi
    block=$(echo -n ${arg^^} | grep -oP '(?<=-)(CBC|CTR|CFB|OFB)(?=\s|$)'); if [[ $block = "" ]]; then block=CBC; fi
    len=$(du -b "$in" | awk '{print $1}')
    if (( $len < 65536 )); then 
      read -s -r -p "Passwort: " Pas; echo; read -s -r -p "nochmal : " Pas2; echo
      if [ "$Pas" = "$Pas2" ] && (( ${#Pas} > 0 )); then
        if [[ $arg =~ -e ]]; then #------------------ Encrypt -------------------
          echo Encrypt AES${aes}-${block} '>' "${out}"
          kcload=$(echo -n ${arg} | grep -oiP '(?<=-kcload\s)\s*[a-f\d]{1,4}') #Startadresse für KC bestimmen
          if [[ "$kcload" != "" ]]; then kcload=$(printf '%d' 0x${kcload^^}); else kcload=512; fi
          iv=$(openssl rand -hex 16) #Zufallswerte IV     
          salt=$(openssl rand -hex 8); lencr=$(hex16 $len)  #Zufallswerte Salt u. Dateilaenge
          blockaes=$(echo -n ${block:0:2}${aes: -1}|xxd -p) #CB fuer CBC Mode u. letzte Zahl v. aes in HexString Format merken 
          head=4956${iv}53${salt}${lencr}${blockaes}
          hash=$({(echo -n "${Pas}"); (echo -n ${salt}|xxd -p -r)} | openssl dgst -r -sha256) #Hash mit Passwort u. Salt (-r rechts, 16 Byte von links)
          for ((i=24;i>0;i--)); do hash=$((echo -n $(hex16 $i)${hash:0:64}${salt}|xxd -p -r) | openssl dgst -r -sha256); done #24 mal Hash mit Nr., Hash u. Salt
          Key=${hash:0:$((${aes}/4))} #passenden Key je nach Verschlüsselungsstärke (128-256 bit) übernehmen 
          if [[ $block =~ CTR|CFB|OFB ]]; then #in OpenSSL wird Padding nur bei CBC erzeugt, zu Pruefzwecken aber besser
            klar=$(echo -n $(xxd -p "${in}")|sed 's/\s//g') #Space o. Tab entfernen
            pad=$(( ${#klar}/2 )); pad=$(( ${pad}/16*16+16-${pad} ))
            klar=${klar}$(printf "%$(( ${pad}*2 ))s" | sed "s/  /$(printf %02X ${pad})/g")
            openssl enc -e -aes-${aes}-${block} -in <(echo -n $klar|xxd -p -r) -out "${out}" -K ${Key} -iv ${iv} -nosalt 2>/tmp/sslerr #Salt bereits in Key integriert
          else
            openssl enc -e -aes-${aes}-${block} -in "${in}" -out "${out}" -K ${Key} -iv ${iv} -nosalt 2>/tmp/sslerr
          fi
          if [ ! -s /tmp/sslerr ]; then        
            crypt=${head}$(echo -n $(xxd -p "${out}")|sed 's/\s//g') #Datei einlesen und Space o. Tab entfernen
            kchead=$(echo -n ${out^^}|xxd -p) #Dateiname von ASCII to HEX in Großschreibung
            if (( ${#out} > 8 )); then kchead=${kchead:0:16}; fi
            nul=$(printf "%256s" | tr ' ' '0') #256 Nullen fuer 128 = x080 Bytes erzeugen
            kchead=${kchead}${nul} ;kchead=${kchead:0:32}
            kchead=${kchead}02$(hex16 $kcload)$(hex16 $((kcload+((len+48)/16*16)))) # Endadresse: Head + 16er Blöcke
            kchead=${kchead}${nul} ;kchead=${kchead:0:256} #Rest im Head auffüllen
            crypt=${kchead}${crypt}${nul}; crypt=${crypt:0:$((${#crypt}/256*256))} #; echo $((${#crypt}/256*256)); 
            echo -n ${crypt}|xxd -p -r > "${out}"
          else
            echo "OpenSSl Fehler!"
          fi
        else # -------------------------------- Decrypt ---------------------------
          crypt=$(echo -n $(xxd -p "${in}")|sed 's/\s//g') #Space o. Tab entfernen
          if (( $len > 160 )) && [ "${crypt:256:4}" = "4956" ] && [ "${crypt:292:2}" = "53" ]; then #4956 = IV, 53 = S
            iv=${crypt:256}; salt=${iv:38:26}; cr=${iv:64}; iv=${iv:4:32}
            blockaes=$(echo -n ${salt:20:6}|xxd -p -r) #blockaes gleich in ASCII wandeln
            aes=$(echo -n "128 192 256" | grep -oP '\d\d'${blockaes: -1})
            block=$(echo -n "ECB CBC CTR CFB OFB" | grep -oP ${blockaes:0:2}'\w')
            echo Decrypt AES${aes}-${block} '>' "${out}"
            len=$(printf "%d" 0x${salt:18:2}${salt:16:2}); salt=${salt:0:16} #SLT und len ohne Padding
            if [[ $block =~ ECB|CBC ]]; then len=$(( ($len/16*16+16)*2 )); else len=$(( $len*2)); fi  #Padding nur bei ECB o. CBC in OpenSSL beruecksichtigt
            hash=$({(echo -n "${Pas}"); (echo -n ${salt}|xxd -p -r)} | openssl dgst -r -sha256) #-r rechts, 16 Byte von links
            for ((i=24;i>0;i--)); do hash=$((echo -n $(hex16 $i)${hash:0:64}${salt}|xxd -p -r) | openssl dgst -r -sha256); done
            Key=${hash:0:$((${aes}/4))}; cr=${cr:0:$len} #len + evtl. Padding 16*2, KC füllt bei Save letzten Block FF mit Nullen auf
            openssl enc -d -aes-${aes}-${block} -in <(echo -n $cr|xxd -p -r) -out "${out}" -K ${Key} -iv ${iv} -nosalt 2>/tmp/sslerr
            if [ -s /tmp/sslerr ]; then
              echo "OpenSSl- oder Passwortfehler!"
            fi
          else
            echo "Keine KC-Crypt Datei!"
          fi
        fi
      else
        echo "Passwortfehler!"
      fi 
    else
      echo "Eingabedatei für KC-Format zu groß!"
    fi
  else
    echo "-in oder -out fehlen oder fehlerhaft!"
  fi
else
  echo "Zu wenig Parameter!"
fi



