Skip to content

Instantly share code, notes, and snippets.

@84adam
Created October 24, 2024 13:12
Show Gist options
  • Save 84adam/2672207508ea9fe00f2e0a3b3d721318 to your computer and use it in GitHub Desktop.
Save 84adam/2672207508ea9fe00f2e0a3b3d721318 to your computer and use it in GitHub Desktop.
Password Entropy Calculator: Go + WASM (Debian/x86) -- Bash Deployment Script
#!/bin/bash
# Exit on any error
set -e
echo "🚀 Starting Password Entropy Calculator setup..."
# Create project structure
PROJECT_DIR="password-entropy-calc"
mkdir -p $PROJECT_DIR/{cmd/server,wasm,static}
cd $PROJECT_DIR
# Initialize Go module
echo "📝 Initializing Go module..."
go mod init password-entropy-calc
# Add bcrypt dependency
go get golang.org/x/crypto/bcrypt
# Create main.go for WASM
echo "📝 Creating WASM source file..."
cat > wasm/main.go << 'EOF'
package main
import (
"fmt"
"math"
"syscall/js"
"unicode"
"golang.org/x/crypto/bcrypt"
)
// calculateEntropy calculates the entropy of a password
func calculateEntropy( password string) float64 {
if len(password) == 0 {
return 0
}
var (
hasLower bool
hasUpper bool
hasNumber bool
hasSpecial bool
poolSize float64
)
for _, char := range password {
switch {
case unicode.IsLower(char):
hasLower = true
case unicode.IsUpper(char):
hasUpper = true
case unicode.IsNumber(char):
hasNumber = true
case unicode.IsPunct(char) || unicode.IsSymbol(char):
hasSpecial = true
}
}
if hasLower {
poolSize += 26
}
if hasUpper {
poolSize += 26
}
if hasNumber {
poolSize += 10
}
if hasSpecial {
poolSize += 32
}
entropy := math.Log2(math.Pow(poolSize, float64(len(password))))
return entropy
}
// getStrengthDescription returns a description of password strength
func getStrengthDescription(entropy float64) string {
switch {
case entropy < 28:
return "Very Weak"
case entropy < 36:
return " Weak"
case entropy < 60:
return "Moderate"
case entropy < 128:
return "Strong"
default:
return "Very Strong"
}
}
// generateBcryptHash generates a bcrypt hash of the password
func generateBcryptHash(password string) (string, error) {
if password == "" {
return "", nil
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), 13)
if err != nil {
return "", err
}
return string(hash), nil
}
// wrapper function for JavaScript
func calculatePasswordInfoWrapper() js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
return nil
}
password := args[0].String()
entropy := calculateEntropy(password)
strength := getStrengthDescription(entropy)
hash, err := generateBcryptHash(password)
if err != nil {
hash = "Error generating hash"
}
// Create result map
result := make(map[string]interface{})
result["entropy"] = fmt.Sprintf("%.2f bits", entropy)
result["strength"] = strength
result["hash"] = hash
return js.ValueOf(result)
})
}
func main() {
fmt.Println("Password Entropy Calculator initialized")
js.Global().Set("calculatePasswordInfo", calculatePasswordInfoWrapper())
<-make(chan bool)
}
EOF
# Create server.go
echo "📝 Creating server file..."
cat > cmd/server/main.go << 'EOF'
package main
import (
"log"
"net/http"
"os"
"path/filepath"
)
func main() {
exe, err := os.Executable()
if err != nil {
log.Fatal(err)
}
exeDir := filepath.Dir(exe)
staticDir := filepath.Join(exeDir, "static")
fs := http.FileServer(http.Dir(staticDir))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if filepath.Ext(r.URL.Path) == ".wasm" {
w.Header().Set("Content-Type", "application/wasm")
}
fs.ServeHTTP(w, r)
})
log.Printf("Serving files from: %s\n", staticDir)
log.Println("Server starting on http://localhost:8081 🚀")
if err := http.ListenAndServe(":8081", nil); err != nil {
log.Fatal(err)
}
}
EOF
# Create index.html with updated UI
echo "📝 Creating HTML file..."
cat > static/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Password Entropy Calculator</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.input-group {
margin-bottom: 20px;
position: relative;
}
.password-container {
position: relative;
display: flex ;
align-items: center;
}
input[type="password"],
input[type="text"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.toggle-password {
position: absolute;
right: 10px;
background: none;
border: none;
cursor: pointer;
color: #666;
padding: 5px;
}
.toggle-password:hover {
color: #333;
}
.result {
margin-top: 20px;
padding: 15px;
border-radius: 4px;
}
.strength-meter {
height: 10px;
background-color: #eee;
border-radius: 5px;
margin-top: 10px;
}
.strength-meter-fill {
height: 100%;
border-radius: 5px;
transition: width 0.3s ease-in-out;
}
.very-weak { background-color: #ff4 444; }
.weak { background-color: #ffbb33; }
.moderate { background-color: #ffeb3b; }
.strong { background-color: #00C851; }
.very-strong { background-color: #007E33; }
.hash-display {
word-break: break-all;
padding: 10px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #ddd;
margin-top: 10px;
font-family: monospace;
}
</style>
</head>
<body>
<div class="container">
<h1>Password Entropy Calculator</h1>
<div class="input-group">
<div class="password-container">
<input type="password" id="password" placeholder="Type your password...">
<button class="toggle-password" onclick="togglePasswordVisibility()">👁️</button>
</div>
</div>
<div class="result">
<p>Entropy: <span id="entropy">0 bits</span></p>
<p>Strength: <span id="strength"> Very Weak</span></p>
<div class="strength-meter">
<div id="strength-meter-fill" class="strength-meter-fill" style="width: 0%"></div>
</div>
<p>BCrypt Hash (13 rounds):</p>
<div id="hash" class="hash-display">-</div>
</div>
</div>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject)
.then((result) => {
go.run(result.instance);
setupPasswordListener();
})
.catch((err) => {
console.error('Failed to load WASM:', err);
});
function togglePasswordVisibility() {
const passwordInput = document.getElementById('password');
passwordInput.type = passwordInput.type === 'password' ? 'text' : 'password';
}
function setupPasswordListener() {
const passwordInput = document.getElementById('password');
const entropySpan = document.getElementById('entropy');
const strengthSpan = document.getElementById('strength');
const strengthMeterFill = document.getElementById('strength-meter-fill');
const hashDisplay = document.getElementById('hash');
passwordInput.addEventListener('input', function() {
const result = calculatePasswordInfo(this.value);
entropySpan.textContent = result.entropy;
strengthSpan.textContent = result.strength;
hashDisplay.textContent = result.hash || '-';
// Update strength meter
strengthMeterFill.className = 'strength-meter-fill';
let width = 0;
switch(result.strength) {
case 'Very Weak': width = 20; strengthMeterFill.classList.add('very-weak'); break;
case 'Weak': width = 40; strengthMeterFill.classList.add('weak'); break;
case 'Moderate': width = 60; strengthMeterFill.classList.add('moderate'); break;
case 'Strong': width = 80; strengthMeterFill.classList.add('strong'); break;
case 'Very Strong': width = 100; strengthMeterFill.classList.add('very-strong'); break;
}
strengthMeterFill.style.width = width + '%';
});
}
</script>
</body>
</html >
EOF
# Copy wasm_exec.js from Go installation
echo "📦 Copying wasm_exec.js..."
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" static/
# Download required Go modules
echo "📦 Downloading Go dependencies..."
go mod tidy
# Compile the Go program to WASM
echo "🔨 Compiling Go to WASM..."
pushd wasm
GOOS=js GOARCH=wasm go build -o ../static/main.wasm
popd
# Build the server
echo "🔨 Building server..."
pushd cmd/server
go build -o ../../server
popd
# Create deployment directory
echo "📦 Creating deployment directory..."
DEPLOY_DIR="deploy"
mkdir -p $DEPLOY_DIR
cp server $DEPLOY_DIR/
cp -r static $DEPLOY_DIR/
# Install process manager if not installed
if ! command -v pm2 &> /dev/null; then
echo "📦 Installing PM2 process manager..."
if ! command -v npm &> /dev/null; then
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
fi
sudo npm install -g pm2
fi
# Stop any existing instance
pm2 stop entropy-calculator 2>/dev/null || true
pm2 delete entropy-calculator 2>/dev/null || true
# Start the server with PM2
echo "🚀 Starting server..."
cd $DEPLOY_DIR
pm2 start ./server --name entropy-calculator
echo "✨ Setup complete! Password Entropy Calculator is running at http://localhost:8081"
echo "📝 PM2 Logs: pm2 logs entropy-calculator"
echo "⏹️ To stop: pm2 stop entropy-calculator"
@84adam
Copy link
Author

84adam commented Oct 24, 2024

THIS IS A TOY APP. USE A SERIOUS PASSWORD MANAGER TO GENERATE STRONG PASSWORDS IF YOU CARE ABOUT SECURITY.

passwd-screenshot-2

Reference: "correct horse battery staple" from XKCD comic #936: "Password Strength" - https://xkcd.com/936/

Original Image HTML Title: "To anyone who understands information theory and security and is in an infuriating argument with someone who does not (possibly involving mixed case), I sincerely apologize."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment