我正在尝试验证 haskell 中的 ed25519 签名。
为此我创建了一个签名:
$ ssh-keygen -Y sign -f data/testkey_ed25519 -n file data/message.txt
我另外创建了 allowed_signers 文件,因为这是验证所必需的:
# allowed_signers
testprincipal ssh-ed25519 <public key> <comment>
ssh-keygen 然后可以验证签名:
$ ssh-keygen -Y verify -n file -s data/message.txt.sig -f data/authorized_signers -I testprincipal < data/message.txt
Good "file" signature for testprincipal with ED25519 key SHA256:RmoAVYLzWd7b2pTB0O1ovGu/KhXosg0zk++pJgIvQjw
我希望在 haskell 中实现 ssh-keygen 的此功能(无需外部调用),但我无法使用 crypton 重现结果:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C
import qualified Extra
import qualified Crypto.PubKey.Ed25519 as Ed25519
import Crypto.Error
import qualified Data.ByteString.Base64 as Base64
unarmorSignature :: C.ByteString -> C.ByteString
unarmorSignature = removeHeader . removeFooter . removeLinebreaks
where
header = "-----BEGIN SSH SIGNATURE-----"
footer = "-----END SSH SIGNATURE-----"
removeHeader = C.drop (C.length header) . snd . C.breakSubstring header
removeFooter = fst . C.breakSubstring footer
removeLinebreaks = C.filter (/= '\n')
main :: IO ()
main = do
message <- BS.readFile "data/message.txt"
print message
signatureWrapped <- BS.readFile "data/message.txt.sig"
let signatureDecoded = Extra.fromRight' $ Base64.decode $ unarmorSignature signatureWrapped
let signatureBS = BS.takeEnd 64 signatureDecoded
signature <- throwCryptoErrorIO $ Ed25519.signature signatureBS
print signature
keyfileContent <- BS.readFile "data/authorized_signers"
let _:_:key:_ = C.words keyfileContent
let keyDecoded = Extra.fromRight' $ Base64.decode key
let Just keyBS = C.stripPrefix "\NUL\NUL\NUL\vssh-ed25519\NUL\NUL\NUL " keyDecoded
key <- throwCryptoErrorIO $ Ed25519.publicKey keyBS
print key
let result = Ed25519.verify key message signature
print result
代码应该(非常粗略地)解析签名和 allowed_signers 文件格式(根据 RFC8709)并从中提取纯 ed25519 密钥和签名。 然后应该使用它们来验证签名是否是使用密钥和消息创建的。
程序应该在最后打印“true”而不是“false”。
可以在此处
找到完全可重现的示例不幸的是,为了验证签名,签名的数据(
test\n
字符串)无法按原样传递。
根据协议,它应该采用以下格式:
byte[6] MAGIC_PREAMBLE
string namespace
string reserved
string hash_algorithm
string H(message)
message
是此处的 test\n
字符串,但它也应该经过哈希处理并添加附加元数据。
实际打印
True
结果的草稿代码:
...
import qualified Data.ByteString.Base16 as Base16 -- imported base16-bytestring
import Crypto.Hash
...
sha512 :: C.ByteString -> String
sha512 bs = show (hash bs :: Digest SHA512)
intArrayToByteString :: [Int] -> C.ByteString
intArrayToByteString = BS.pack . map fromIntegral
hexStringToIntList :: String -> Either String [Int]
hexStringToIntList hexStr = do
decoded <- Base16.decode (C.pack hexStr)
return $ map fromIntegral (BS.unpack decoded)
...
let hash = Extra.fromRight' $ hexStringToIntList $ sha512 message
let intSignedText = [ 83, 83, 72, 83, 73, 71, 0, 0, 0, 4 -- SSHSIG magic header
, 102, 105, 108, 101 -- namespace ("file")
, 0, 0, 0, 0, 0, 0, 0, 6 -- reserved
, 115, 104, 97, 53, 49, 50, 0, 0, 0, 64 -- hash alg (sha512)
] ++ hash
let result = Ed25519.verify key (intArrayToByteString intSignedText) signature
print result