読者です 読者をやめる 読者になる 読者になる

0x90

一回休み

Haskellでセキュリティフォントを復号してみる

セキュリティフォントとかいうのが話題になってましたね。

実はあれは単純なシーザー暗号じゃないらしい(マイナンバーごとにテーブルを変えている)とかいう話もあるんですが、単純なシーザーだと思ったときじゃあ何個マイナンバーを集めれば復号化できるかな?と思ってHaskellでプログラムを書いてみました。

予想

チェックディジットを通る確率はざっくり10分の1で、テーブルはだいたい10! ~ 107候補があるので、まあ7,8個くらいあれば一意に定まるんじゃないかという予想。

コード

コードはここにぶっこみました。

GitHub - hajifkd/securefont

説明

流石にUTSLすぎワロタな記事になっていたので追加。

以下のコードでは、適当にマイナンバーを作って適当なキーで暗号化しています(encode.hs)。 それを総当りで復号化するのがdecode.hsです。どちらも普通にコンパイルできると思います。 randomとrandom-shuffleを使ったのでstack installしておいてください。 実行は

$ ./encode 生成するマイナンバー数 > s.dat
$ ./decode 復号するマイナンバー数 < s.dat

とかすると真のキーと見つかったキー候補が表示されます。

まずはマイナンバー関連の処理。

module Common where

enc = "ABCDEFGHIJ"

checkDigit :: [Int] -> Int
checkDigit ps = 11 - pq
  where
    pq      = if pqmod <= 1 then 11 else pqmod
    pqmod   = pqsum `mod` 11
    pqsum   = sumpqs ps 11
    sumpqs (p:ps) n = p * (if n <= 6 then n + 1 else n - 5) + sumpqs ps (n - 1)
    sumpqs [] _     = 0

続いて、適当なマイナンバーを作る処理。

import System.Random
import System.Random.Shuffle (shuffleM)
import Control.Monad
import System.Environment (getArgs)
import Data.Array
import Common

correctMyNumber :: [Int] -> [Int]
correctMyNumber ns = ns ++ [checkDigit ns]

shuffleEnc :: IO String
shuffleEnc = shuffleM enc

main :: IO ()
main = do
  times   <- read . head <$> getArgs
  mnss    <- replicateM times $ replicateM 11 $ (getStdRandom $ randomR (0, 9))
  sencl   <- shuffleEnc
  putStrLn $ "Table: " ++ sencl
  let senc = listArray (0, 9) sencl
  mapM_ (putStrLn . map (senc !) . correctMyNumber) mnss

最後にデコード。

import Control.Monad
import System.Environment (getArgs)
import Data.List (permutations)
import Data.Array
import Common


isValidMns :: [Int] -> Bool
isValidMns mns = checkDigit mnheads == mntail
  where
    (mnheads, [mntail]) = splitAt 11 mns

decode :: [String] -> String -> [[Int]]
decode emns table = [[tablea ! e | e <- emn] | emn <- emns]
  where
    tablea = array ('A', 'J') $ zip table [0..]

main :: IO ()
main = do
  header  <- getLine
  putStrLn header
  times   <- read . head <$> getArgs
  mns     <- replicateM times getLine
  let tables = filter (all isValidMns . decode mns) $ permutations enc
  mapM_ putStrLn tables

マジックナンバーだらけのくっそ汚いコードですいません。

結果

6個集まると候補が5,6個になり、7個では大体一意に定まるようです。

眠いのでなんでそうなったかはまたあとで考えますw