Skip to content

Instantly share code, notes, and snippets.

@tinbotu
Created January 31, 2026 20:17
Show Gist options
  • Select an option

  • Save tinbotu/8236e48ff1b3bf3857130e13d8507ad7 to your computer and use it in GitHub Desktop.

Select an option

Save tinbotu/8236e48ff1b3bf3857130e13d8507ad7 to your computer and use it in GitHub Desktop.
フィクション作中作向けツール: 人間にとってランダム感のあるランダムパスワード生成 (NEVER USE THIS TO GENERATE **REALPASSWORD** !!!)
#!/usr/bin/env python3
"""
Pseudo Password Generator
A tool to generate strings that "look random" to the human eye for use in fiction.
Prioritizes visual randomness over statistical randomness.
"""
import argparse
import math
import random
import sys
from collections import Counter
from typing import Tuple
# Available characters
CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
# Characters allowed for the first position (letters only)
CHARSET_FIRST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
# Vowels (cause strings to look too readable)
VOWELS = set("aeiouAEIOU")
# Keyboard layout sequences
KEYBOARD_SEQUENCES = [
"qwerty", "asdf", "zxcv", "qwer", "asdfgh", "zxcvbn",
"yuiop", "hjkl", "bnm", "qazwsx", "edcrfv", "tgbyhn",
]
# Common sequences
COMMON_SEQUENCES = [
"abc", "bcd", "cde", "def", "efg", "fgh", "ghi", "hij", "ijk", "jkl",
"klm", "lmn", "mno", "nop", "opq", "pqr", "qrs", "rst", "stu", "tuv",
"uvw", "vwx", "wxy", "xyz",
"012", "123", "234", "345", "456", "567", "678", "789", "890",
"321", "432", "543", "654", "765", "876", "987",
]
# Leet speak mappings (patterns to avoid)
LEET_PATTERNS = [
("a", "4"), ("e", "3"), ("i", "1"), ("o", "0"), ("s", "5"),
("t", "7"), ("b", "8"), ("g", "9"), ("l", "1"),
]
def has_consecutive_chars(s: str, max_repeat: int = 2) -> bool:
"""Check if the string contains consecutive identical characters."""
for i in range(len(s) - max_repeat + 1):
if len(set(s[i:i + max_repeat])) == 1:
return True
return False
def has_sequence(s: str) -> bool:
"""Check if the string contains keyboard or common sequences."""
s_lower = s.lower()
for seq in KEYBOARD_SEQUENCES + COMMON_SEQUENCES:
if seq in s_lower:
return True
return False
def is_palindrome_like(s: str, min_len: int = 4) -> bool:
"""Check if the string contains palindromic patterns."""
s_lower = s.lower()
for length in range(min_len, len(s) + 1):
for i in range(len(s) - length + 1):
substr = s_lower[i:i + length]
if substr == substr[::-1]:
return True
return False
def has_pronounceable_pattern(s: str, max_syllables: int = 3) -> bool:
"""Check if the string contains pronounceable patterns (consonant-vowel sequences)."""
s_lower = s.lower()
# Count consecutive consonant-vowel patterns
cv_count = 0
i = 0
while i < len(s_lower) - 1:
char = s_lower[i]
next_char = s_lower[i + 1]
if char.isalpha() and next_char.isalpha():
if char not in VOWELS and next_char in VOWELS:
cv_count += 1
i += 2
continue
cv_count = 0
i += 1
if cv_count >= max_syllables:
return True
return False
def has_leet_pattern(s: str) -> bool:
"""Check if the string contains leet speak patterns."""
s_lower = s.lower()
# Check if typical leet substitutions are adjacent
for i in range(len(s) - 1):
for letter, digit in LEET_PATTERNS:
# Letter and its corresponding digit are adjacent
pair = s_lower[i:i + 2]
if pair == letter + digit or pair == digit + letter:
return True
return False
def has_too_many_vowels(s: str, max_ratio: float = 0.4) -> bool:
"""Check if the string has too many vowels, making it too readable."""
alpha_chars = [c for c in s if c.isalpha()]
if not alpha_chars:
return False
vowel_count = sum(1 for c in alpha_chars if c in VOWELS)
return vowel_count / len(alpha_chars) > max_ratio
def has_visual_similarity_cluster(s: str) -> bool:
"""Check if the string contains clusters of visually similar characters."""
similar_groups = [
set("Il1|"),
set("O0"),
set("S5"),
set("Z2"),
set("B8"),
set("G6"),
]
for i in range(len(s) - 2):
window = s[i:i + 3]
for group in similar_groups:
matches = sum(1 for c in window if c in group)
if matches >= 2:
return True
return False
def is_visually_random(s: str) -> bool:
"""Comprehensive check if the string appears visually random."""
if has_consecutive_chars(s):
return False
if has_sequence(s):
return False
if is_palindrome_like(s):
return False
if has_pronounceable_pattern(s):
return False
if has_leet_pattern(s):
return False
if has_too_many_vowels(s):
return False
if has_visual_similarity_cluster(s):
return False
return True
def generate_candidate(length: int) -> str:
"""Generate a candidate string."""
if length < 1:
return ""
# First character must be a letter
first_char = random.choice(CHARSET_FIRST)
rest = ''.join(random.choice(CHARSET) for _ in range(length - 1))
return first_char + rest
def calculate_entropy(s: str) -> float:
"""
Calculate the Shannon entropy of a string (in bits).
Computes information content based on character frequency.
"""
if not s:
return 0.0
counter = Counter(s)
length = len(s)
entropy = 0.0
for count in counter.values():
probability = count / length
entropy -= probability * math.log2(probability)
# Total entropy of the string (in bits)
return entropy * length
def calculate_max_entropy(length: int, charset_size: int = len(CHARSET)) -> float:
"""
Calculate the theoretical maximum entropy.
This is the entropy when all characters appear with equal probability.
"""
# Entropy per character = log2(charset_size)
# Total entropy = log2(charset_size) * length
return math.log2(charset_size) * length
def get_entropy_stats(s: str) -> Tuple[float, float, float]:
"""
Get entropy statistics.
Returns: (actual entropy, maximum entropy, percentage)
"""
actual = calculate_entropy(s)
maximum = calculate_max_entropy(len(s))
percentage = (actual / maximum * 100) if maximum > 0 else 0.0
return actual, maximum, percentage
def generate_visually_random_string(length: int, max_attempts: int = 2000) -> str:
"""Generate a visually random string."""
for _ in range(max_attempts):
candidate = generate_candidate(length)
if is_visually_random(candidate):
return candidate
# Worst case: return the last candidate with a warning
print(f"Warning: Could not generate ideal string after {max_attempts} attempts",
file=sys.stderr)
return candidate
def main():
parser = argparse.ArgumentParser(
description='Generate strings that look random to the human eye for use in fiction'
)
parser.add_argument(
'length',
type=int,
nargs='?',
default=16,
help='length of the generated string (default: 16)'
)
parser.add_argument(
'count',
type=int,
nargs='?',
default=10,
help='number of strings to generate (default: 10)'
)
args = parser.parse_args()
length = args.length
count = args.count
if not (8 <= length <= 32):
print("Warning: length should be between 8 and 32", file=sys.stderr)
if count < 1:
parser.error("count must be at least 1")
max_entropy = calculate_max_entropy(length)
print(f"# Charset: {len(CHARSET)} chars, Length: {length}, Max Entropy: {max_entropy:.2f} bits")
print()
for _ in range(count):
result = generate_visually_random_string(length)
actual, maximum, percentage = get_entropy_stats(result)
print(f"{result} [{actual:.1f}/{maximum:.1f} bits = {percentage:.1f}%]")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment