Raw code | Youtube demonstration
Note: this is NOT SECURE ENCRYPTION. It is fun to watch and play with, but it is NOT SECURE.

#!/usr/bin/env php
<?php 
$doSubstitution 
= true;

function 
resetSpeed() {
    global 
$speed, $speedslowfactor, $speedmin;
    
$speed = 400;
    
$speedslowfactor = 1.1;
    
$speedmin = 10;
}
resetSpeed();

$encrypt = true;
echo 
"Encrypt (enter E) or decrypt (enter D)? ";
$encryptInput = stream_get_line(STDIN, 1024, PHP_EOL);
if (
$encryptInput == 'd' || $encryptInput == 'D') {
    
$encrypt = false;
}

echo 
"Input password, end with enter. Press Ctrl+D to abort.\n";
$password = stream_get_line(STDIN, 1024, PHP_EOL);
if (!
$password) {
    die(
"Ctrl+D - exiting\n");
}

echo 
"Input message, end with enter. Press Ctrl+D to abort.\n";
$message = stream_get_line(STDIN, 1024, PHP_EOL);
if (!
$message) {
    die(
"Ctrl+D - exiting\n");
}

$closestSquare = ceil(sqrt(strlen($message)));
$padding = '';
for (
$i = 0; $i < pow($closestSquare, 2) - strlen($message) - 1; $i++) {
    
$padding .= chr(rand(32, 126));
}
if (
strlen($padding) > 0) {
    
$padding = ' ' . $padding;
}
$message .= $padding;

if (
$encrypt) {
    echo 
"Starting encryption. Our square is:\n";
    
displaySquare($message, $closestSquare);
    echo 
"Expanding password... ";
    
$password = keyExpansion($password);
    echo 
"New password: $password\n";

    if (
$doSubstitution) {
        echo 
"Applying password to each character...\n";
        for (
$i = 0; $i < strlen($message); $i++) {
            
$passindex = $i % strlen($password);
            
$passchar = $password[$passindex];
            
$newchar = chr((((ord($message[$i]) - 32) + (ord($passchar) - 32) + $i) % 95) + 32);
            echo 
"Char $i (" . $message[$i] . ', #' . (ord($message[$i]) - 32) . ") plus password char $passindex ($passchar, #" . (ord($passchar) - 32) . ") plus position $i results in $newchar (#" . (ord($newchar) - 32) . ")\n";
            
$message[$i] = $newchar;
            
displaySquare($message, $closestSquare);
        }
        
resetSpeed();
        echo 
"Done applying character substitution. Now let's shift our square around!\n";
    }
    else {
        echo 
"Skipping substitution as requested...\n";
    }

    
$sq = strToSq($message, $closestSquare);
    for (
$i = 0; $i < strlen($password) - 1; $i += 2) {
        
$passchars = $password[$i] . $password[$i + 1];
        
$passvalue = (ord($password[$i]) - 32) * 96 + ord($password[$i + 1]) - 32;
        
$dir        = $passvalue & bindec('000000000001');
        
$horizshift = $passvalue & bindec('000000000010') >> 1;
        
$row        = $passvalue & bindec('000001111100') >> 2;
        
$amount     = $passvalue & bindec('111110000000') >> 7;
        
$amount    += $i;
        
$row       %= $closestSquare;
        
$dir        = $dir > 0 ? 1 : -1;
        
$horizshift = $horizshift > 0;
        
$amount    %= $closestSquare;
        
$amount    *= $dir;
        
$rowcol     = $horizshift ? 'row' : 'column';
        echo 
"Password chars $i and " . ($i + 1) .  " ($passchars) make #" . ($passvalue - 32) . " which give us $rowcol $row.\nWe will shift $amount times.\n";
        if (
$horizshift) {
            
$original = $sq[$row];
            for (
$j = 0; $j < $closestSquare; $j++) {
                
$index = ($j + $amount) % $closestSquare;
                if (
$index < 0) {
                    
$index += $closestSquare;
                }
                
$sq[$row][$j] = $original[$index];
            }
        }
        else {
            
$original = [];
            for (
$j = 0; $j < $closestSquare; $j++) {
                
$original[] = $sq[$j][$row];
            }

            for (
$j = 0; $j < $closestSquare; $j++) {
                
$index = ($j + $amount) % $closestSquare;
                if (
$index < 0) {
                    
$index += $closestSquare;
                }
                
$sq[$j][$row] = $original[$index];
            }
        }
        
displaySquare2($sq);
    }

    echo 
"All done!\n";
    echo 
"Result: ";
    for (
$x = 0; $x < $closestSquare; $x++) {
        for (
$y = 0; $y < $closestSquare; $y++) {
            echo 
$sq[$y][$x];
        }
    }
    echo 
"\n";
}
else {
    echo 
"Starting decryption. Our square is:\n";
    
$sq = strToSqRev($message, $closestSquare);
    
displaySquare2($sq);

    
$originalPassword = $password; // We need this later for substitution
    
$password = keyExpansion($password);
    echo 
"Expanding password... new password: $password\n";

    
$actionqueue = [];
    for (
$i = 0; $i < strlen($password) - 1; $i += 2) {
        
$passchars = $password[$i] . $password[$i + 1];
        
$passvalue = (ord($password[$i]) - 32) * 96 + ord($password[$i + 1]) - 32;
        
$dir        = $passvalue & bindec('000000000001');
        
$horizshift = $passvalue & bindec('000000000010') >> 1;
        
$row        = $passvalue & bindec('000001111100') >> 2;
        
$amount     = $passvalue & bindec('111110000000') >> 7;
        
$amount    += $i;
        
$row       %= $closestSquare;
        
$dir        = $dir > 0 ? 1 : -1;
        
$amount    %= $closestSquare;
        
$amount    *= $dir;
        
$actionqueue[] = [-$amount, $horizshift, $row, $dir, $passchars, $passvalue];
    }

    
$actionqueue = array_reverse($actionqueue);

    
$i = 0;
    foreach (
$actionqueue as $action) {
        
$amount = $action[0];
        
$horizshift = $action[1];
        
$row = $action[2];
        
$dir = $action[3];
        
$passchars = $action[4];
        
$passvalue = $action[5];
        
$horizshift = $horizshift > 0 ? true : false;
        
$rowcol = $horizshift ? 'row' : 'column';
        echo 
"Password chars $i and " . ($i + 1) .  " ($passchars) make #" . ($passvalue - 32) . " which give us $rowcol $row.\nWe will shift $amount times.\n";
        
$i++;
        if (
$dir > 0) {
            
$original = $sq[$row];
            for (
$j = 0; $j < $closestSquare; $j++) {
                
$index = ($j + $amount) % $closestSquare;
                if (
$index < 0) {
                    
$index += $closestSquare;
                }
                
$sq[$row][$j] = $original[$index];
            }
        }
        else {
            
$original = [];
            for (
$j = 0; $j < $closestSquare; $j++) {
                
$original[] = $sq[$j][$row];
            }

            for (
$j = 0; $j < $closestSquare; $j++) {
                
$index = ($j + $amount) % $closestSquare;
                if (
$index < 0) {
                    
$index += $closestSquare;
                }
                
$sq[$j][$row] = $original[$index];
            }
        }
        
displaySquare2($sq);
    }

    
$message = '';
    foreach (
$sq as $y) {
        
$message .= implode('', $y);
    }

    if (
$doSubstitution) {
        
resetSpeed();
        echo 
"Done shifting things around. Now reversing character substitution...\n";

        
// Apply substitution
        
for ($i = 0; $i < strlen($message); $i++) {
            
$passindex = $i % strlen($password);
            
$passchar = $password[$passindex];
            
$newchar = chr(mod(((ord($message[$i]) - 32) - (ord($passchar) - 32) - $i), 95) + 32);
            echo 
"Char $i (" . $message[$i] . ', #' . (ord($message[$i]) - 32) . ") minus password char $passindex ($passchar, #" . (ord($passchar) - 32) . ") minus position $i results in $newchar (#" . (ord($newchar) - 32) . ")\n";
            
$message[$i] = $newchar;
            
displaySquare($message, $closestSquare);
        }
    }
    else {
        echo 
"Skipping substitution as requested\n";
    }

    
// Done!
    
echo "Done! Result: $message\n";
    echo 
"(Note that the last few characters are probably garbage.)\n";
}

function 
mod($n, $m) {
    
$n = $n % $m;
    if (
$n > 0)
        return 
$n;

    return (
$n + abs($m)) % $m;
}

// Same as strToSq() except this reverts the x and y axis
function strToSqRev($str, $size) {
    
$sq = [];
    for (
$x = 0; $x < $size; $x++) {
        
$tmpstr = substr($str, $x * $size, $size);
        for (
$y = 0; $y < strlen($tmpstr); $y++) {
            if (!isset(
$sq[$y])) {
                
$sq[$y] = [];
            }
            
$sq[$y][$x] = $tmpstr[$y];
        }
    }
    return 
$sq;
}

function 
strToSq($str, $size) {
    
$sq = [];
    for (
$y = 0; $y < $size; $y++) {
        
$tmpstr = substr($str, $y * $size, $size);
        
$sq[$y] = [];
        for (
$x = 0; $x < strlen($tmpstr); $x++) {
            
$sq[$y][$x] = $tmpstr[$x];
        }
    }
    return 
$sq;
}

function 
displaySquare($message, $size) {
    global 
$speed, $speedslowfactor, $speedmin;
    
$outstr = '';
    for (
$y = 0; $y < $size; $y++) {
        
$str = substr($message, $y * $size, $size);
        for (
$i = 0; $i < strlen($str); $i++) {
            
$outstr .= $str[$i] . ' ';
        }
        
$outstr .= "\n";
    }
    echo 
$outstr . "\n";
    
usleep($speed * 1000);
    
$speed = max($speedmin, $speed / $speedslowfactor);
}

function 
displaySquare2($sq) {
    global 
$speed, $speedslowfactor, $speedmin;
    
$outstr = '';
    foreach (
$sq as $y) {
        foreach (
$y as $x) {
            
$outstr .= $x . ' ';
        }
        
$outstr .= "\n";
    }
    echo 
$outstr . "\n";
    
usleep($speed * 1000);
    
$speed = max($speedmin, $speed / $speedslowfactor);
}

function 
keyExpansion($password) {
    
$password .= $password;
    
$originalPassword = $password;
    
$i = ord($password[0]) + 1;
    while (
strlen($originalPassword) > 1) {
        
$password .= str_repeat($originalPassword . chr(($i % 95) + 32), strlen($originalPassword));
        
$originalPassword = substr($originalPassword, 1);
        
$i++;
    }
    
$a = 43690;
    for (
$i = 0; $i < strlen($password) - 1; $i++) {
        
$password[$i] = chr((((ord($password[$i]) ^ $a) + $i) % 95) + 32);
        if (
$a == 43690) {
            
$a = 21845;
        }
        else {
            
$a = 43690;
        }
    }
    return 
$password;
}