Created
October 24, 2024 13:12
-
-
Save 84adam/2672207508ea9fe00f2e0a3b3d721318 to your computer and use it in GitHub Desktop.
Password Entropy Calculator: Go + WASM (Debian/x86) -- Bash Deployment Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
THIS IS A TOY APP. USE A SERIOUS PASSWORD MANAGER TO GENERATE STRONG PASSWORDS IF YOU CARE ABOUT SECURITY.
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."