Powered by AppSignal & Oban Pro

Chinese Password Generation with ExkPasswd

notebooks/i18n_chinese.livemd

Chinese Password Generation with ExkPasswd

Mix.install([
  {:exk_passwd, "~> 0.1.0"}
])

Introduction

This notebook demonstrates how to generate memorable Chinese passwords that output as ASCII Pinyin for universal keyboard compatibility.

The Problem

Most password systems worldwide (including in China) require ASCII-only passwords, but:

  • Chinese users want memorable passwords in their native language
  • Typing Chinese characters requires specific input methods
  • International travel means accessing accounts on non-Chinese keyboards

The Solution

  1. Dictionary: Chinese words (memorable for Chinese speakers)
  2. Transform: Pinyin romanization (ASCII output, 500+ characters supported)
  3. Result: Typeable on any keyboard, memorable in Chinese

Features:

  • Covers 500+ most frequent Chinese characters (Jun Da frequency list)
  • IME-compatible ü/v mapping (lv, nv but ju, xu, yu)
  • Helper functions: contains_hanzi?/1, hanzi?/1

Setup: Load Chinese Dictionary

# Common Chinese words for passwords
chinese_words = [
  "中国",  # China
  "世界",  # World
  "你好",  # Hello
  "朋友",  # Friend
  "爱情",  # Love
  "家人",  # Family
  "快乐",  # Happy
  "美好",  # Beautiful
  "春天",  # Spring
  "夏天",  # Summer
  "秋天",  # Autumn
  "冬天",  # Winter
  "太阳",  # Sun
  "月亮",  # Moon
  "星星",  # Stars
  "山水",  # Mountains and water
  "花朵",  # Flowers
  "树木",  # Trees
  "和平",  # Peace
  "希望"   # Hope
]

# Load into ExkPasswd
ExkPasswd.Dictionary.load_custom(:chinese, chinese_words)

IO.puts("✓ Loaded #{length(chinese_words)} Chinese words")

Configure Password Generation

config = ExkPasswd.Config.new!(
  # Use Chinese dictionary
  dictionary: :chinese,

  # Chinese words are typically 2 characters
  word_length: 2..2,

  # Override English default (4-10) to allow shorter words
  word_length_bounds: 1..10,

  # Use 3 words for good memorability and security
  num_words: 3,

  # ASCII separator (compatible everywhere)
  separator: "-",

  # Add digits for additional entropy
  digits: {2, 2},

  # No padding (keep it clean)
  padding: %{char: "", before: 0, after: 0, to_length: 0},

  # No case transforms (Chinese has no uppercase/lowercase)
  case_transform: :none,

  # Apply Pinyin transform for ASCII output
  meta: %{
    transforms: [%ExkPasswd.Transform.Pinyin{}]
  }
)

IO.puts("✓ Configuration created")

Generate Passwords

IO.puts("\n=== Generated Passwords ===\n")

for i <- 1..10 do
  password = ExkPasswd.generate(config)
  IO.puts("#{i}. #{password}")
end

How It Works

The generation process:

graph LR
    A[Chinese Words] --> B[Random Selection]
    B --> C[中国-世界-你好]
    C --> D[Pinyin Transform]
    D --> E[zhongguo-shijie-nihao]
    E --> F[Add Digits]
    F --> G[45-zhongguo-shijie-nihao-89]

Internal representation: 中国-世界-你好 Output: 45-zhongguo-shijie-nihao-89

Memorable: Chinese speaker remembers the meaning ✅ Compatible: Works on any keyboard, any system ✅ Secure: Cryptographically random word selection

Security Analysis

sample_password = ExkPasswd.generate(config)

IO.puts("\n=== Security Analysis ===")
IO.puts("Sample password: #{sample_password}")
IO.puts("")
IO.puts("Dictionary size: #{length(chinese_words)} words")
IO.puts("Entropy per word: #{Float.round(:math.log2(length(chinese_words)), 2)} bits")
IO.puts("Total word entropy: #{Float.round(:math.log2(length(chinese_words)) * 3, 2)} bits")
IO.puts("Additional entropy: Digits (2 before + 2 after) = ~13.3 bits")
IO.puts("Total entropy: ~#{Float.round(:math.log2(length(chinese_words)) * 3 + 13.3, 1)} bits")
IO.puts("")
IO.puts("Status: Good security for most accounts")
IO.puts("Recommendation: Add more words to dictionary for higher security")

Test Pinyin Transform

Test the Pinyin transform directly:

alias ExkPasswd.Transform.Pinyin

transform = %Pinyin{}

test_words = [
  {"中国", "zhongguo"},
  {"世界", "shijie"},
  {"你好", "nihao"},
  {"朋友", "pengyou"},
  {"春天", "chuntian"},
  {"女人", "nvren"},      # ü after n → v
  {"旅行", "lvxing"},     # ü after l → v
  {"学习", "xuexi"},      # ü after x → u
  {"下雨", "xiayu"}       # ü after y → u
]

IO.puts("\n=== Pinyin Transform Test ===\n")

for {chinese, expected} <- test_words do
  result = ExkPasswd.Transform.apply(transform, chinese, nil)
  match = if result == expected, do: "✓", else: "✗"
  IO.puts("#{match} #{chinese}#{result} (expected: #{expected})")
end

Polyphone limitation (多音字): each character maps to its single most common pronunciation, so context-dependent readings are not disambiguated. For example 乐 always renders as le (as in 快乐 → kuaile), so 音乐 renders as yinle rather than the linguistically correct yinyue. For password generation this is fine — output only needs to be consistent, not phonetically perfect. See the Polyphone Handling section in the ExkPasswd.Transform.Pinyin module docs.

Hanzi Detection Helpers

The Pinyin module provides helper functions for detecting Chinese characters:

alias ExkPasswd.Transform.Pinyin

IO.puts("\n=== Hanzi Detection ===\n")

# Check if text contains Chinese characters
IO.puts("contains_hanzi?(\"Hello世界\") = #{Pinyin.contains_hanzi?("Hello世界")}")
IO.puts("contains_hanzi?(\"Hello World\") = #{Pinyin.contains_hanzi?("Hello World")}")

# Check if a single character is Hanzi
IO.puts("\nhanzi?(\"\") = #{Pinyin.hanzi?("中")}")
IO.puts("hanzi?(\"A\") = #{Pinyin.hanzi?("A")}")
IO.puts("hanzi?(\"\") = #{Pinyin.hanzi?("あ")}  # Hiragana")

# Character coverage
map_size = map_size(Pinyin.pinyin_map())
IO.puts("\nPinyin map contains #{map_size} characters")
IO.puts("Covers the top 500 most frequent Chinese characters, plus additional common ones")

Tip: characters outside the mapping pass through unchanged, which would leave non-ASCII characters in your password. Before using a custom word list, verify it converts cleanly:

Enum.filter(chinese_words, fn word ->
  ExkPasswd.Transform.apply(%Pinyin{}, word, nil) =~ ~r/[^a-z]/
end)
# => [] when every word is fully covered

Customization Examples

More Words for Higher Security

config_secure = ExkPasswd.Config.new!(
  dictionary: :chinese,
  word_length: 2..2,
  word_length_bounds: 1..10,
  num_words: 5,  # More words = more entropy
  separator: "-",
  digits: {3, 3},  # More digits
  padding: %{char: "", before: 0, after: 0, to_length: 0},
  case_transform: :none,
  meta: %{transforms: [%ExkPasswd.Transform.Pinyin{}]}
)

IO.puts("High security password:")
IO.puts(ExkPasswd.generate(config_secure))

Shorter for Website Limits

config_short = ExkPasswd.Config.new!(
  dictionary: :chinese,
  word_length: 2..2,
  word_length_bounds: 1..10,
  num_words: 2,  # Fewer words
  separator: "",  # No separator
  digits: {2, 2},
  padding: %{char: "", before: 0, after: 0, to_length: 0},
  case_transform: :none,
  meta: %{transforms: [%ExkPasswd.Transform.Pinyin{}]}
)

IO.puts("Short password:")
IO.puts(ExkPasswd.generate(config_short))

Real-World Usage

Example: Chinese User Traveling

Scenario: Li Wei is traveling to the US and needs to access Alipay on a hotel computer.

Without ExkPasswd:

  • Password: 中国世界2023!
  • Problem: Hotel computer has no Chinese input method ❌

With ExkPasswd:

  • Password: 45-zhongguo-shijie-nihao-89
  • Memorized as: “中国世界你好” (China World Hello)
  • Types on QWERTY: Easy! ✅

Key Takeaways

  1. Most systems require ASCII - Even Chinese banks use ASCII-only passwords
  2. Memorability + Compatibility - Chinese words transformed to Pinyin
  3. Cryptographically secure - Random selection, not predictable
  4. Works everywhere - Any keyboard, any system, any country

Next Steps

  • Add more Chinese words to your dictionary for higher entropy
  • Try Japanese with Romaji: See notebooks/i18n_japanese.livemd
  • Build custom transforms for other languages
  • Integrate into your password manager workflow