A simple encoder/decoder using Caesar's Cipher algorithm, where you can dynamically change the shift amount. It's also made with flexbox, so, assured to be responsive in most of the screens!
A Pen by Guilherme Aguiar on CodePen.
A simple encoder/decoder using Caesar's Cipher algorithm, where you can dynamically change the shift amount. It's also made with flexbox, so, assured to be responsive in most of the screens!
A Pen by Guilherme Aguiar on CodePen.
<div class="container" ng-app="app" ng-controller="caesarController"> | |
<div class="input"> | |
<form name="inputForm"> | |
<div class="row"> | |
<label for="shiftKey">Shift Key:</label> | |
<input type="number" min="1" max="25" id="shiftKey" name="shiftKey" ng-model="shiftKey" integer required> | |
</div> | |
<div class="row"> | |
<label for="inputText">Input:</label> | |
<textarea id="inputText" name="inputText" ng-model="inputText" cols="40" rows="7" required></textarea> | |
</div> | |
<div class="row button"> | |
<button ng-click="inputForm.$valid && shift(1)">Encode</button> | |
<button ng-click="crack()">Crack</button> | |
<button ng-click="inputForm.$valid && shift(-1)">Decode</button> | |
</div> | |
</form> | |
</div> | |
<div class="output"> | |
<div class="row"> | |
<label for="outputText">Output:</label> | |
<textarea id="outputText" name="outputText" ng-model="outputText" cols="40" rows="7"></textarea> | |
</div> | |
<div class="row button"> | |
<button onclick="copyToClipboard('.output > .row > textarea')">Copy</button> | |
<button ng-click="sendToInput()">Send to Input</button> | |
</div> | |
</div> | |
<div class="output" ng-hide="!tries.length"> | |
<div class="row"> | |
<h3>Entropy Levels:</h3> | |
<div ng-repeat="n in tries track by $index"> | |
<div class="progress-bar-indication"> | |
<span class="meter" ng-style="{width: (n[1] / tries[tries.length - 1][1] * 100).toFixed(6) + '%'}"> | |
<p>Shift {{26 - n[0]}}: {{n[1].toFixed(3)}}</p> | |
</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="footer"> | |
<p>2016 - Made by <a href="http://guiguetz.github.io/">Guilherme Aguiar</a>.</p> | |
</div> |
// App Declaration | |
let app = angular.module('app', []); | |
/***/ | |
// Controller dedicated to Caesar's cipher | |
app.controller('caesarController', function($scope) { | |
$scope.inputText = "Distantes estão os caminhos que vão para o Tempo — outro luar eu vi passar na altura\nNas plagas verdes as mesmas lamentações escuto como vindas da eterna espera\nO vento ríspido agita sombras de araucárias em corpos nus unidos se amando\nE no meu ser todas as agitações se anulam como as vozes dos campos moribundos."; | |
$scope.shiftKey = 1; | |
$scope.shift = function(v) { | |
$scope.outputText = caesarShift(removeAccents($scope.inputText), $scope.shiftKey * v); | |
}; | |
$scope.sendToInput = function() { | |
$scope.inputText = $scope.outputText; | |
}; | |
$scope.crack = function() { | |
$scope.tries = crackShift($scope.inputText); | |
let bestShift = 26 - $scope.tries[0][0]; | |
$scope.outputText = caesarShift(removeAccents($scope.inputText), -bestShift); | |
}; | |
}); | |
// Directive to validate integer number at shift | |
let INTEGER_REGEXP = /^-?\d+$/; | |
app.directive('integer', function() { | |
return { | |
require: 'ngModel', | |
link: function(scope, elm, attrs, ctrl) { | |
ctrl.$validators.integer = function(modelValue, viewValue) { | |
if (ctrl.$isEmpty(modelValue)) { | |
// consider empty models to be valid | |
return true; | |
} | |
if (INTEGER_REGEXP.test(viewValue)) { | |
// it is valid | |
return true; | |
} | |
// it is invalid | |
return false; | |
}; | |
} | |
}; | |
}); | |
function caesarShift(str, key) { | |
if (key < 0) | |
return caesarShift(str, key + 26); | |
let output = ''; | |
for (let i = 0; i < str.length; i ++) { | |
let c = str[i]; | |
if (c.match(/[a-z]/i)) { | |
let code = str.charCodeAt(i); | |
if ((code >= 65) && (code <= 90)) | |
c = String.fromCharCode(((code - 65 + key) % 26) + 65); | |
else if ((code >= 97) && (code <= 122)) | |
c = String.fromCharCode(((code - 97 + key) % 26) + 97); | |
} | |
output += c; | |
} | |
return output; | |
} | |
function crackShift(str) { | |
let res = getAllEntropies(str); | |
res.sort((x, y) => { | |
if (x[1] < y[1]) return -1; | |
else if (x[1] > y[1]) return 1; | |
else if (x[0] < y[0]) return -1; | |
else if (x[0] > y[0]) return 1; | |
return 0; | |
}); | |
let bestShift = 26 - res[0][0]; | |
return res; | |
} | |
function getEntropy(str) { | |
let freqs = [0.1463, 0.0104, 0.0388, 0.0499, 0.1257, 0.0102, 0.0130, 0.0128, 0.0618, 0.0040, 0.0002, 0.0278, 0.0474,0.0505, 0.1073, 0.0252, 0.0120, 0.0653, 0.0781, 0.0434, 0.0463, 0.0167, 0.0001, 0.0021, 0.0001, 0.0047]; | |
let sum = 0; | |
let ignored = 0; | |
for (let i = 0; i < str.length; i++) { | |
let c = str.charCodeAt(i); | |
if (c >= 65 && c <= 90) sum += Math.log(freqs[c - 65]); // Uppercase | |
else if (c >= 97 && c <= 122) sum += Math.log(freqs[c - 97]); // Lowercase | |
else ignored++; | |
} | |
return -sum / Math.log(2) / (str.length - ignored); | |
} | |
function getAllEntropies(str) { | |
let result = new Array(26); | |
for (let i = 0; i < 26; i++) | |
result[i] = [i, getEntropy(caesarShift(str, i))]; | |
return result; | |
} | |
// Utils | |
function copyToClipboard(element) { | |
let $temp = $("<textarea>"); | |
$("body").append($temp); | |
$temp.val($(element).val()).select(); | |
document.execCommand("copy"); | |
$temp.remove(); | |
} | |
function removeAccents(str) { | |
let accents = 'ÀÁÂÃÄÅàáâãäåßÒÓÔÕÕÖØòóôõöøĎďDŽdžÈÉÊËèéêëðÇçČčÐÌÍÎÏìíîïÙÚÛÜùúûüĽĹľĺÑŇňñŔ੹ŤťŸÝÿýŽž'; | |
let accentsOut = "AAAAAAaaaaaasOOOOOOOooooooDdDZdzEEEEeeeeeCcCcDIIIIiiiiUUUUuuuuLLllNNnnRrSsTtYYyyZz"; | |
str = str.split(''); | |
let strLen = str.length; | |
let x; | |
for (let i=0; i<strLen; i++) { | |
if ((x = accents.indexOf(str[i])) != -1) { | |
str[i] = accentsOut[x]; | |
} | |
} | |
return str.join(''); | |
} |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.6/angular.js"></script> |
@import "bourbon@5.*" | |
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700") | |
$default-color: #02556E | |
$button-color: #2E2E2E | |
$text-color: #BDD2D9 | |
$error-color: #fbe3e4 | |
$success-color: #e6efc2 | |
%flex-container | |
display: flex | |
flex-direction: column | |
flex-wrap: wrap | |
justify-content: space-evenly | |
align-items: stretch | |
@media (max-width: 768px) | |
justify-content: center | |
body | |
background-color: $default-color | |
@extend %flex-container | |
flex-direction: row | |
align-items: stretch | |
height: 100vh | |
font-family: 'Open Sans', sans-serif | |
color: $text-color | |
a | |
color: $text-color | |
text-decoration: none | |
&:hover | |
color: tint($text-color, 80%) | |
.footer | |
@extend %flex-container | |
.ng-invalid.ng-touched | |
background-color: $error-color | |
.container | |
@extend %flex-container | |
width: 100% | |
@media (max-width: 768px) | |
align-items: center | |
.input,.output | |
background-color: rgba(white, 0.1) | |
@extend %flex-container | |
margin: 1.5% | |
padding: 2.5% | |
border-radius: 15px | |
@media (max-width: 768px) | |
width: 85% | |
.row,.button | |
@extend %flex-container | |
.button | |
flex-direction: row | |
button | |
background-color: $button-color | |
width: 150px | |
padding: 10px | |
margin-top: 20px | |
border-radius: 10px | |
border: none | |
color: $text-color | |
font-weight: 700 | |
&:hover | |
background-color: tint($button-color, 5%) | |
.progress-bar-indication | |
$base-border-color: gainsboro !default | |
$base-border-radius: 3px !default | |
$base-background-color: white !default | |
$base-line-height: 1.5em !default | |
$action-color: #477DCA !default | |
$progress-border-color: $base-border-color | |
$progress-border: 1px solid $progress-border-color | |
$progress-meter-border-color: $action-color | |
$progress-meter-border: 1px solid darken($progress-meter-border-color, 15%) | |
$progress-meter-color: $progress-meter-border-color | |
$progress-background: darken($base-background-color, 5) | |
$progress-animation-duration: 0.7s | |
$progress-color: white | |
background-color: $progress-background | |
border-radius: $base-border-radius | |
border: $progress-border | |
box-shadow: inset 0 0 3px 0 rgba(darken($progress-background, 50%), 0.15) | |
margin: 2px auto | |
width: 100% | |
span.meter | |
background-color: $progress-meter-color | |
background-repeat: repeat-x | |
background-size: 40px 40px | |
border: $progress-meter-border | |
border-bottom-right-radius: 0 | |
border-radius: $base-border-radius /1.5 | |
border-top-right-radius: 0 | |
box-sizing: border-box | |
display: block | |
height: $base-line-height + 0.3 | |
p | |
color: $progress-color | |
line-height: $base-line-height | |
margin: 0 | |
padding: 0.1em 0.5em | |
text-shadow: 0 0 1px black |