Implementoval jsem kontrolu elektronické značky v C++ pomocí CryptoAPI a narazil při tom na značné problémy, které se sice daly obejít, ale podle mého názoru ukazují na to, že kontrola elektronické značky je implementována značně nestandardně.
Na adrese https://bezpecne.dev.gov.cz/diskuze/forums/144/ShowPost.aspx je uveden demonstrační testovací klient elektronické značky, psaný v C# a používající knihovnu CAPICOM. Na první pohled se takto „shora“ v .NET jeví vše v pořádku – používají se stringy, porovnají se, vše sedí a je hotovo.
Pokud se však na kód podíváme hlouběji, opustíme .NET a implementujeme vše bez CAPICOM, ale s pomocí CryptoAPI, začneme používat místo stringů bajty – a to je přesnější, neboť hash je binární kód a nikoli string – a najednou narazíme. Když získáme z podpisu co podepsal (tedy původní hash) pomocí funkce
(1) CryptVerifyMessageSignature(...)
zjistíme, že výsledek má délku nikoli 20 bajtů, jak bychom očekávali u SHA1 algoritmu, ale plných 80 bajtů! Kde se vzaly?
Odpověď nalezneme, pokud nejprve vytvoříme hash z dokumentu submit acknowledgement (po odstranění elementu < SignatureValue>, ale i po odstranění hlavičky „<?xml version="1.0"?>“ - což mimochodem nikde v dokumentaci není uvedeno) , pomocí
(2) CryptCreateHash(...), CryptHashData(...), CryptGetHashParam(...)
Výsledek má správných 20 bajtů. Tak jak s ním porovnat 80 bajtů získaných z podpisu? Inu, musíme s nimi trochu nestandardně začachrovat. Těch 20 bajtů převedema nejprve na hexa zápis, ano, nikoli třeba na base64, jak by snad někdo čekal. Získali jsme 40 bajtů. Ještě musíme přidat. Jak? Převedeme už takto získaný hexa zápis, který by již byl korektním stringem, ještě navíc na unicode (UTF-16), čímž sice každý znak zbytečně zdvojíme, ale získali jsme požadovanou délku 80.
Nyní již stačí porovnat výsledek (1) a (2), což odpovídá postupu ve výše uvedeném příkladu z .NET, a rázem vše sedí – hash získaná z dokumentu je stejná, jako hash získaná z podpisu.
Na co to poukazuje? Na to, že podpis elektronické značky není standardním podpisem hash (výsledku algoritmu SHA1), ale podpisem hash převedené na hexa zápis a navíc ještě do unicode (UTF-16). Obojí naprosto zbytečně.
Důsledkem toho je například nemožnost použít CryptoAPI funkci
(3) CryptVerifyDetachedMessageSignature(...)
která zapouzdřuje (1) a (2), ale uvnitř pracuje standardně, tj. vytvoří si hash dokumentu, získá hash z podpisu, a výsledek nikdy nesedí - 0x80090006 = NTE_BAD_SIGNATURE. Ani sedět nemůže, protože délka hash z podpisu (80) a délka hash z dokumentu (20) je vždy různá.
Pavel B.