SHA-256 Pure Perl

Das Programm liest eine Datei ein, deren Name beim Start als Parameter übergeben wird. Es verarbeitet den Inhalt der Datei nach dem offiziellen SHA-256 Standard: Zuerst wird der Text gepolstert (Padding), dann in 512-Bit-Blöcke zerlegt und jeder Block durchläuft 64 Rechenschritte mit Bitoperationen wie Rotation, XOR und logischen Verknüpfungen. Die dabei entstehenden Zwischenwerte werden in acht 32-Bit-Variablen gespeichert und nach jeder Runde aktualisiert. Am Ende ergibt sich ein 256-Bit-Hashwert, der als 64-stellige Hex-Zeichenkette ausgegeben wird. Die Besonderheit: Alles geschieht vollständig in Perl, ohne externe Bibliotheken – ideal zum Verstehen des Algorithmus.

#!/usr/bin/perl
# SHA-256 Pure Perl implementation

use strict;
use warnings;

# Initial-Hashwerte H0–H7 (32 Bit)
# Sie stammen von den ersten 32 Bits der Quadratwurzeln der ersten 8 Primzahlen
my @H = (
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
);

# 64 Rundenschlüssel (Konstanten K0–K63)
# Sie stammen aus den ersten 32 Bits der Kubikwurzeln der ersten 64 Primzahlen
my @K = (
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
);

# Bitweise Rechtsrotation um $n Bits bei 32-Bit-Wert
# Beispiel: rotr(0b10110000, 2) → 0b00101100
sub rotr {
    my ($x, $n) = @_;
    return (($x >> $n) | ($x << (32 - $n))) & 0xffffffff;
}

# SHA-256 Kernfunktion
sub sha256 {
    my ($msg) = @_;
    my $bitlen = length($msg) * 8;

    # Padding nach SHA256-Spezifikation: 1-Bit, gefolgt von 0-Bits, dann Länge
    $msg .= chr(0x80);
    $msg .= chr(0x00) while length($msg) % 64 != 56;
    $msg .= pack("Q>", $bitlen);  # 64-Bit-Länge big-endian

    my @h = @H;  # Arbeitskopie der Initial-Hashwerte

    for (my $i = 0; $i < length($msg); $i += 64) {
        my $chunk = substr($msg, $i, 64);
        my @w = unpack("N16", $chunk);  # 16 Wörter à 32 Bit

        # Erweiterung auf 64 Wörter mit Bitoperationen
        for my $t (16 .. 63) {
            my $s0 = rotr($w[$t-15],7) ^ rotr($w[$t-15],18) ^ ($w[$t-15] >> 3);
            my $s1 = rotr($w[$t-2],17) ^ rotr($w[$t-2],19) ^ ($w[$t-2] >> 10);
            $w[$t] = ($w[$t-16] + $s0 + $w[$t-7] + $s1) & 0xffffffff;
        }

        # Initialisierung der Arbeitsvariablen a–h
        my ($a,$b,$c,$d,$e,$f,$g,$h_) = @h;

        # 64 Kompressionsrunden
        for my $t (0 .. 63) {
            # S1 = Kombination aus 3 Rotationen von e
            my $S1 = rotr($e,6) ^ rotr($e,11) ^ rotr($e,25);
            # ch = bedingte Auswahl: if e then f else g
            my $ch = ($e & $f) ^ ((~$e) & $g);
            my $temp1 = ($h_ + $S1 + $ch + $K[$t] + $w[$t]) & 0xffffffff;

            # S0 = 3 Rotationen von a
            my $S0 = rotr($a,2) ^ rotr($a,13) ^ rotr($a,22);
            # maj = Mehrheit von a, b, c
            my $maj = ($a & $b) ^ ($a & $c) ^ ($b & $c);
            my $temp2 = ($S0 + $maj) & 0xffffffff;

            # Schiebe die Arbeitsvariablen weiter
            ($h_,$g,$f,$e,$d,$c,$b,$a) =
                ($g,$f,$e,($d + $temp1) & 0xffffffff,
                 $c,$b,$a,($temp1 + $temp2) & 0xffffffff);
        }

        # Addiere Blockresultat zum aktuellen Hashwert
        my @temp = ($a,$b,$c,$d,$e,$f,$g,$h_);
        @h = map { ($h[$_] + $temp[$_]) & 0xffffffff } (0..7);
    }

    # Finale Ausgabe als hexadezimale Zeichenkette (8×32 Bit → 64 Hex-Zeichen)
    return join('', map { sprintf("%08x", $_) } @h);
}

# Kommandozeilenargument: Dateiname
my $filename = shift;

# Prüfung: Wurde überhaupt ein Dateiname übergeben?
if (!$filename) {
    die "FEHLER: Bitte gib den Dateinamen als Parameter an.\nBeispiel: ./sha256-pure_perl.pl test.txt\n";
}

# Prüfung: Existiert die Datei wirklich?
if (!-f $filename) {
    die "FEHLER: Datei '$filename' existiert nicht oder ist ungültig.\n";
}

# Datei binär öffnen und Inhalt vollständig lesen
open(my $fh, '<', $filename) or die "Kann Datei '$filename' nicht öffnen: $!";
binmode($fh);
my $data = do { local $/; <$fh> };
close($fh);

# SHA256 berechnen & ausgeben
my $hash = sha256($data);
print "SHA256('$filename') = $hash\n";