Powered by AppSignal & Oban Pro

中文密码与拼音输出

notebooks/i18n_chinese.livemd

中文密码与拼音输出

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

This notebook uses Chinese words as the memorable source and emits toneless ASCII Pinyin. The bundled transform is intentionally small and deterministic; validate every word in a real dictionary before use.

Load an example dictionary

chinese_words = [
  "中国", # China
  "世界", # world
  "你好", # hello
  "朋友", # friend
  "爱情", # love
  "家人", # family
  "快乐", # happiness
  "美好", # beautiful/good
  "春天", # spring
  "夏天", # summer
  "秋天", # autumn
  "冬天", # winter
  "太阳", # sun
  "月亮", # moon
  "星星", # stars
  "山水", # landscape
  "花朵", # flowers
  "树木", # trees
  "和平", # peace
  "希望"  # hope
]

:ok = ExkPasswd.Dictionary.load_custom(:chinese_demo, chinese_words)

The example has only 20 choices. It demonstrates the API; it is not a production-sized password dictionary.

Verify the conversion

transform = %ExkPasswd.Transform.Pinyin{}

converted =
  Map.new(chinese_words, fn word ->
    {word, ExkPasswd.Transform.apply(transform, word, nil)}
  end)

unmapped = Enum.filter(converted, fn {_source, output} -> output =~ ~r/[^a-z]/ end)
collisions = converted |> Map.values() |> Enum.frequencies() |> Enum.filter(fn {_, n} -> n > 1 end)

%{converted: converted, unmapped: unmapped, collisions: collisions}

Require unmapped == [] when ASCII output is a hard requirement. Review collisions as well: toneless Pinyin is many-to-one, so different source words can become the same password component.

Generate passwords

config =
  ExkPasswd.Config.new!(
    dictionary: :chinese_demo,
    word_length: 2..2,
    word_length_bounds: 1..10,
    num_words: 3,
    separator: "-",
    digits: {2, 2},
    padding: %{char: "", before: 0, after: 0, to_length: 0},
    case_transform: :none,
    meta: %{transforms: [transform]}
  )

for _ <- 1..10, do: ExkPasswd.generate(config)

Examples have the shape 45-zhongguo-shijie-nihao-89. The Chinese meaning is the memory aid; the stored password is the ASCII output.

Entropy of this example

details = ExkPasswd.Entropy.calculate_seen_detailed(config)

%{
  word_entropy: Float.round(details.word_entropy, 2),
  digit_entropy: Float.round(details.digit_entropy, 2),
  total: Float.round(details.total, 2),
  rating: ExkPasswd.Strength.rating(ExkPasswd.generate(config), config)
}

With 20 unique transformed choices, three words and four digits provide about 26.25 bits. That is intentionally shown as a weak example. Increasing only the number of selected words helps, but a substantially larger, reviewed dictionary is the better starting point.

larger_config = ExkPasswd.Config.merge!(config, num_words: 5, digits: {3, 3})
Float.round(ExkPasswd.Entropy.calculate_seen(larger_config), 2)

The 5-word/6-digit variant is about 41.54 bits for this tiny dictionary. Do not describe it as stronger than the calculation supports.

Pronunciation checks

examples = [
  {"中国", "zhongguo"},
  {"世界", "shijie"},
  {"你好", "nihao"},
  {"朋友", "pengyou"},
  {"春天", "chuntian"},
  {"女人", "nvren"},
  {"旅行", "lvxing"},
  {"学习", "xuexi"},
  {"下雨", "xiayu"}
]

for {source, expected} <- examples do
  actual = ExkPasswd.Transform.apply(transform, source, nil)
  {source, actual, actual == expected}
end

The v spelling for ü after n and l follows common keyboard input conventions (nv, lv). After j, q, x, and y, Pinyin orthography uses u (ju, qu, xu, yu).

Important linguistic limitations

  • Conversion is character-by-character and omits tones.
  • Context-dependent readings are not disambiguated. For example, 乐 is always mapped to le, so 音乐 becomes yinle rather than yinyue.
  • The map currently contains 674 characters. It is not the top 500 entries of Jun Da’s frequency list: an audit found 87 of those top 500 characters absent.
  • Against the audited frequency data, the mapped characters cover about 75.6% of corpus occurrences, not 95%.
  • Traditional characters and rare simplified characters often pass through.

The 674 supplied mappings were checked against Unicode Unihan kMandarin primary readings; 671 match directly, while 地 (di), 长 (chang), and 嗯 (en) use defensible alternate readings. That check supports the entries that exist, not a broad coverage claim.

Detection helpers

alias ExkPasswd.Transform.Pinyin

%{
  map_size: map_size(Pinyin.pinyin_map()),
  contains_hanzi: Pinyin.contains_hanzi?("Hello世界"),
  single_hanzi: Pinyin.hanzi?("中"),
  latin_is_hanzi: Pinyin.hanzi?("A")
}

For a production integration, add a reviewed application-specific dictionary, assert ASCII conversion and output uniqueness during deployment, and calculate entropy from the final configured transform pipeline.