🔑

openssl equivalentなAESをchromeでもPowershellでも

2021/02/07に公開

導入

  • 社用PCに勝手なアプリケーションを入れないのは、大人の嗜みである。
  • プレーンなPC環境でも、パスワードのメモとかは暗号化しておきたい。
  • chromeのAPIは使うがjsライブラリは使わない方向で。
  • opensslと同じ動きにして、相互運用出来ると便利かも。
  • (免責)この実装でセキュリティを担保出来る保証はありません。

encrypt

まずCLI

  echo "message" | openssl enc -aes-256-cbc -a -pbkdf2 -md sha512 -p -pass pass:hoge

これと等価なことをjavascript(chromeでのみ確認)でやる。

  c={text:"message",algenc:"AES-CBC",algbit:256,algkd:"pbkdf2",alghash:"SHA-512",kdi:10000,c:crypto,u:Uint8Array,e:x=>(new TextEncoder()).encode(x)};
  c.passbin=(new TextEncoder()).encode("hoge");
  c.salt=c.c.getRandomValues(new c.u(8))
  c.c.subtle.importKey("raw",c.passbin,{name:c.algkd},!1,["deriveBits"])
    .then(km => c.c.subtle.deriveBits( { name: c.algkd, salt: c.salt, iterations: c.kdi, hash: c.alghash }, km, 8*(32+16) )) // key(32)+iv(16)
    .then(b => (c.db=b)&&(c.iv=b.slice(32,48))&&c.c.subtle.importKey("raw",b.slice(0,32),c.algenc,!1,["encrypt"]))
    .then(k  => (c.key=k)&&c.c.subtle.encrypt({name:c.algenc,iv:c.iv},c.key,c.e(c.text)))
    .then(e=>c.encrypted=window.btoa(String.fromCharCode(...([...c.e("Salted__"),...c.salt,...(new c.u(e))]))))
    .finally(()=>console.log(c.encrypted))

powershellでもやる。

  function enc($s, $p){
    $salt = New-Object byte[] 8
    $rng  = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
    $rng.getBytes($salt)
    $kiv  = (New-Object System.Security.Cryptography.Rfc2898DeriveBytes([System.Text.Encoding]::UTF8.GetBytes($p), $salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA512)).GetBytes(48)
    $aes = New-Object System.Security.Cryptography.AesManaged
    $aes.KeySize   = 256
    $aes.BlockSize = 128
    $aes.Mode      = [System.Security.Cryptography.CipherMode]::CBC
    $aes.Padding   = [System.Security.Cryptography.PaddingMode]::PKCS7
    $aes.Key       = $kiv[0..31]
    $aes.IV        = $kiv[32..47]
    $encryptor = $aes.CreateEncryptor($aes.Key, $aes.IV)
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($s)
    $ms = [System.IO.MemoryStream]::new()
    $ms.Write([System.Text.Encoding]::UTF8.GetBytes("Salted__") + $salt, 0, 16)
    $cs = New-Object System.Security.Cryptography.CryptoStream($ms, $encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
    $cs.Write($bytes, 0, $bytes.length)
    $cs.FlushFinalBlock()
    $cs.Close()
    [System.Convert]::ToBase64String($ms.ToArray())
    $ms.Close()
    $encryptor.Dispose()
    $aes.Dispose()
  }
  enc "message" "hoge"

decrypt

そしてcliでdecrypt。

echo "U2FsdGVkX1+6SGosjqp5QKdNOwVIn5KAvOQtTqObCAE=" | openssl enc -aes-256-cbc -a -d -pbkdf2 -md sha512 -p -pass pass:hoge

等価なjavascript

  c={enctext:"U2FsdGVkX1+6SGosjqp5QKdNOwVIn5KAvOQtTqObCAE=",algenc:"AES-CBC",algbit:256,algkd:"pbkdf2",alghash:"SHA-512",kdi:10000,c:crypto,u:Uint8Array};
  c.passbin=(new TextEncoder()).encode("hoge");
  c.encbin=new c.u(window.atob(c.enctext).split("").map(a=>a.charCodeAt(0)));
  c.salt=c.encbin.slice(8,16);
  c.c.subtle.importKey("raw",c.passbin,{name:c.algkd},!1,["deriveBits"])
    .then(km => c.c.subtle.deriveBits( { name: c.algkd, salt: c.salt, iterations: c.kdi, hash: c.alghash }, km, 8*(32+16)) ) // key(32)+iv(16)
    .then(b => (c.db=b)&&(c.iv=b.slice(32,48))&&c.c.subtle.importKey("raw",b.slice(0,32),c.algenc,!1,["decrypt"]))
    .then(k  => (c.key=k)&&c.c.subtle.decrypt({name:c.algenc,iv:c.iv},c.key,c.encbin.slice(16)))
    .then(d=>c.decodedtext=(new TextDecoder("utf-8")).decode(new c.u(d)))
    .finally(()=>console.log(c.decodedtext))

powershell

  function dec($s, $p){ # base64str
    $encText = [System.Convert]::FromBase64String($s);
    $salt = $encText[8..15]
    $kiv  = (New-Object System.Security.Cryptography.Rfc2898DeriveBytes([System.Text.Encoding]::UTF8.GetBytes($p), $salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA512)).GetBytes(48)
    $aes = New-Object System.Security.Cryptography.AesManaged
    $aes.KeySize   = 256
    $aes.BlockSize = 128
    $aes.Mode      = [System.Security.Cryptography.CipherMode]::CBC
    $aes.Padding   = [System.Security.Cryptography.PaddingMode]::PKCS7
    $aes.Key       = $kiv[0..31]
    $aes.IV        = $kiv[32..47]
    $decryptor = $aes.CreateDecryptor()
    $ms1 = [System.IO.MemoryStream]::new($encText[16..($encText.Length-1)])
    $cs = New-Object System.Security.Cryptography.CryptoStream($ms1, $decryptor, [System.Security.Cryptography.CryptoStreamMode]::Read)
    $ms2 = [System.IO.MemoryStream]::new()
    $cs.CopyTo($ms2)
    $ms1.Close()
    $ms2.Close()
    $cs.Close()
    [System.Text.Encoding]::UTF8.GetString($ms2.ToArray())
    $decryptor.Dispose()
    $aes.Dispose()
  }
  dec "U2FsdGVkX1+6SGosjqp5QKdNOwVIn5KAvOQtTqObCAE=" "hoge"

学び

  • 毎回encrypt結果が異なるのはランダムなソルトが付加されているため
  • opensslの標準形式は Salted__ という文字列とソルト値(8bytes)を最初に付加してくる
  • ソルトが8bytesで十分なのか調べたら16bytes以上がいいよ、みたいな情報があったがopensslは過去との互換性でこのフォーマットを変えられないっぽく、将来のIssueになっていた
  • ベータ中のopenssl 3.0をコンパイルして動作を見てみたけど、openssl 3.0でも8bytesソルトが標準形式っぽかった。
  • encryptにopenssl使わないなら標準形式に拘らなくてもよかった。

Discussion