let internal (@@) a1 a2 = Array.append a1 a2
- let generatKeysPair : Key * Key = Crypto.generateRSAKeysPair
+ let test = 256 / 8
+ let hmacSize = 256 / 8 // [byte].
+ let signatureSize = Crypto.rsaKeySize / 8 // [byte].
+ let keysSize = Crypto.rsaKeySize / 8 // [byte].
+ let generatKeysPair () : Key * Key = Crypto.generateRSAKeysPair ()
+
+ // Format of the container:
+ // <mac><signature><encrypted keys><cyphertext>
+ // Where the sizes of the three first parts are given by 'hmacSize', 'signatureSize' and 'keysSize'.
let encryptFile (inputFilePath : string) (outputFilePath : string) (signaturePrivKey: Key) (cryptPubKey : Key) =
let keyAES, keyMAC, iv = Crypto.rand 16, Crypto.rand 32, Crypto.rand 16
let fileInfo = FileInfo (inputFilePath)
use outputStream = new FileStream (outputFilePath, FileMode.Create, FileAccess.Write)
use writer = new BinaryWriter (outputStream)
- outputStream.Position <- 32L + 256L // Skips mac and signature. They will be written later.
+ outputStream.Position <- (int64 <| hmacSize + signatureSize) // Skips mac and signature. They will be written later.
Crypto.encryptRSA cryptPubKey (keyAES @@ keyMAC @@ iv) |> writer.Write
let decryptFile (sourceFilePath : string) (targetDirPath : string) (signaturePubKey: Key) (decryptPrivKey : Key) =
use inputStream = new FileStream (sourceFilePath, FileMode.Open, FileAccess.Read)
use reader = new BinaryReader (inputStream)
- let mac = reader.ReadBytes 32
- let signature = reader.ReadBytes 256
+ let mac = reader.ReadBytes hmacSize
+ let signature = reader.ReadBytes signatureSize
let keys =
- try reader.ReadBytes 256 |> Crypto.decryptRSA decryptPrivKey
+ try reader.ReadBytes keysSize |> Crypto.decryptRSA decryptPrivKey
with
- | :? Security.Cryptography.CryptographicException -> raise UnableToDecryptAESKeys
+ | :? Security.Cryptography.CryptographicException -> raise UnableToDecryptKeys
let keyAES = keys.[0..15]
let keyMAC = keys.[16..47]
let iv = keys.[48..63]
raise SignatureMismatch
// Decrypt metadata.
- inputStream.Position <- 32L + 256L + 256L
+ inputStream.Position <- (int64 <| hmacSize + signatureSize + keysSize)
use cryptoStream = Crypto.decryptAES keyAES iv inputStream
let metadata = Metadata cryptoStream
let filePath = Path.Combine (targetDirPath, metadata.get MetadataKeys.filename)
let modificationTime = DateTime (metadata.get MetadataKeys.modificationTime |> int64)
let fileInfo = FileInfo filePath
- using (fileInfo.Create ()) <| fun outputStream -> cryptoStream.CopyTo outputStream
+ using (fileInfo.Create ()) cryptoStream.CopyTo // We have to close the result file before updating the modification time.
fileInfo.LastWriteTimeUtc <- modificationTime
\ No newline at end of file
open System.IO
open System.Security.Cryptography
-// Some cryptography primitives specific to CryptoFile.
+// Some cryptography primitives specific to 'CryptoFile'.
module internal Crypto =
type Data = byte[]
- let rsaKeySize = 2048
- let aesKeySize = 128
+ let rsaKeySize = 3072 // [bit]. For encrypting and signing.
+ let aesKeySize = 128 // [bit].
exception KeySizeError
exception IVSizeError
- /// Returns a cryptographically strong sequence of bytes.
+ /// Return a cryptographically strong sequence of bytes.
let rand size : byte[] =
let result = Array.zeroCreate size
use generator = new RNGCryptoServiceProvider ()
generator.GetBytes result
result
- /// Generates a new RSA key pair: (public * private).
- let generateRSAKeysPair : Key * Key =
+ /// Generate a new RSA key pair: (public * private).
+ let generateRSAKeysPair () : Key * Key =
use rsa = new RSACryptoServiceProvider (rsaKeySize)
rsa.ToXmlString false, rsa.ToXmlString true
let encryptRSA (publicKey: Key) (plaindata: Data) : Data =
use rsa = new RSACryptoServiceProvider (rsaKeySize)
rsa.FromXmlString publicKey
- rsa.Encrypt (plaindata, true) // Uses padding OAEP (PKCS#1 v2).
+ rsa.Encrypt (plaindata, true) // Use padding OAEP (PKCS#1 v2).
let decryptRSA (privateKey: Key) (cipherdata: Data) : Data =
use rsa = new RSACryptoServiceProvider (rsaKeySize)
rsa.FromXmlString privateKey
- rsa.Decrypt (cipherdata, true) // Uses padding OAEP (PKCS#1 v2).
+ rsa.Decrypt (cipherdata, true) // Use padding OAEP (PKCS#1 v2).
- /// Produces a signature from a given hash.
- let signRSA (privKey: Key) (sha256: Data) : Data =
+ /// Produce a signature from the given data.
+ let signRSA (privKey: Key) (data: Data) : Data =
use rsa = new RSACryptoServiceProvider (rsaKeySize)
rsa.FromXmlString privKey
- rsa.SignHash (sha256, CryptoConfig.MapNameToOID "SHA256")
+ rsa.SignHash (data, CryptoConfig.MapNameToOID "SHA256")
- /// Verify a signature against a given hash.
- let verifySignRSA (pubKey: Key) (sha256: Data) (signature: Data) : bool =
+ /// Verify a signature against the given data.
+ let verifySignRSA (pubKey: Key) (data: Data) (signature: Data) : bool =
use rsa = new RSACryptoServiceProvider (rsaKeySize)
rsa.FromXmlString pubKey
- rsa.VerifyHash (sha256, CryptoConfig.MapNameToOID "SHA256", signature)
+ rsa.VerifyHash (data, CryptoConfig.MapNameToOID "SHA256", signature)
- /// Returns an encrypted output stream.
+ /// Return an encrypted output stream.
let encryptAES (key: byte[]) (iv: byte[]) (outputStream: Stream) : CryptoStream =
if key.Length <> aesKeySize / 8 then raise KeySizeError
if iv.Length <> 16 then raise IVSizeError
use aes = new AesCryptoServiceProvider (KeySize = aesKeySize) // Default mode is CBC.
new CryptoStream (outputStream, aes.CreateEncryptor (key, iv), CryptoStreamMode.Write)
- /// Returns a decrypted input stream.
+ /// Return a decrypted input stream.
let decryptAES (key: byte[]) (iv: byte[]) (inputStream: Stream) : CryptoStream =
if key.Length <> aesKeySize / 8 then raise KeySizeError
if iv.Length <> 16 then raise IVSizeError
use aes = new AesCryptoServiceProvider (KeySize = aesKeySize)
new CryptoStream (inputStream, aes.CreateDecryptor (key, iv), CryptoStreamMode.Read)
- // Creates a stream to compute the HMAC-SHA256 against all data being written.
+ // Create a stream to compute the HMAC-SHA256 against all data being written.
let HMACStream (key: byte[]) (outputStream: Stream) : Stream * HMACSHA256 =
if key.Length <> 32 then raise KeySizeError
let hmac = new HMACSHA256 (key)
exception IntegrityError
exception SignatureMismatch
-exception UnableToDecryptAESKeys
+exception UnableToDecryptKeys
open Crypto
let testRSA () =
- let kpub, kpriv = generateRSAKeysPair
+ let kpub, kpriv = generateRSAKeysPair ()
let plaintext = "Are you from the past?"
let cipherdata = encryptRSA kpub (Encoding.UTF8.GetBytes plaintext)
let decryptedData = decryptRSA kpriv cipherdata
printfn "testRSA OK"
let testRSASignature () =
- let kpub, kpriv = generateRSAKeysPair
+ let kpub, kpriv = generateRSAKeysPair ()
let plaintext = "Lightspeed is too slow. We'll have to go right to ludicrous speed!"
use sha256 = new SHA256Managed ()
let signature = signRSA kpriv (sha256.ComputeHash (Encoding.UTF8.GetBytes plaintext))
module CryptoFileTests.Main
open System
-open System.IO;
+open System.IO
open CryptoFile
let printUsage () =
// The private keys are stored in plain file! Do not let anyone access these files!
module internal Keys =
// Try to read the public key and private key from files. If one of a file doesn't exist
- // a new key pair is created, stored the returned.
+ // a new key pair is created, stored then returned.
let getKey (filenamePub: string) (filenamePriv: string) : Key * Key =
try
use srPub = new StreamReader (filenamePub)
| _ ->
use swPub = new StreamWriter (filenamePub)
use swPriv = new StreamWriter (filenamePriv)
- let keyPub, keyPriv = API.generatKeysPair
+ let keyPub, keyPriv = API.generatKeysPair ()
swPub.Write keyPub
swPriv.Write keyPriv
keyPub, keyPriv
let doSomeTests () =
printfn "===== Unit tests"
- CryptoFile.UnitTests.runAllUnitTests ()
+ UnitTests.runAllUnitTests ()
printfn "===== Unit tests OK"
printfn "===== API tests"
File.WriteAllText (plainFilename, fileContent)
- let keyCryptPub, keyCryptPriv = API.generatKeysPair
- let keySigPub, keySigPriv = API.generatKeysPair
+ let keyCryptPub, keyCryptPriv = API.generatKeysPair ()
+ let keySigPub, keySigPriv = API.generatKeysPair ()
let encrypt () =
API.encryptFile plainFilename cipherFilename keySigPriv keyCryptPub
API.decryptFile cipherFilename "." keySigPub keyCryptPriv
let incrementByteCipherFileAt position =
- using (new FileStream (cipherFilename, FileMode.Open, FileAccess.ReadWrite))
- (fun fs -> fs.Position <- position
- let byte = fs.ReadByte () |> byte
- fs.Position <- position
- fs.Write ([| byte + 1uy |], 0, 1)) // Automatically modulo 256.
+ use fs = new FileStream (cipherFilename, FileMode.Open, FileAccess.ReadWrite)
+ fs.Position <- position
+ let byte = fs.ReadByte () |> byte
+ fs.Position <- position
+ fs.Write ([| byte + 1uy |], 0, 1) // Modulo 256.
encrypt ()
File.Delete plainFilename
assert (File.ReadAllText plainFilename = fileContent)
printfn "== Altering the MAC..."
- incrementByteCipherFileAt 0L
- try
- decrypt ()
- assert false
- with
- | error -> assert (error :? IntegrityError)
+ for i in 0 .. API.hmacSize - 1 do
+ printf "."
+ encrypt ()
+ incrementByteCipherFileAt (int64 i)
+ try
+ decrypt ()
+ assert false
+ with
+ | error -> assert (error :? IntegrityError)
+ printfn ""
printfn "== Altering the signature..."
- encrypt ()
- incrementByteCipherFileAt 32L
- try
- decrypt ()
- assert false
- with
- | error -> assert (error :? SignatureMismatch)
+ for i in 0 .. API.signatureSize - 1 do
+ printf "."
+ encrypt ()
+ incrementByteCipherFileAt (int64 <| API.hmacSize + i)
+ try
+ decrypt ()
+ assert false
+ with
+ | error -> assert (error :? SignatureMismatch)
+ printfn ""
printfn "== Altering the keys..."
- encrypt ()
- incrementByteCipherFileAt (32L + 256L)
- try
- decrypt ()
- assert false
- with
- | error -> assert (error :? UnableToDecryptAESKeys)
+ for i in 0 .. API.keysSize - 1 do
+ printf "."
+ encrypt ()
+ incrementByteCipherFileAt (int64 <| API.hmacSize + API.signatureSize + i)
+ try
+ decrypt ()
+ assert false
+ with
+ | error -> assert (error :? UnableToDecryptKeys)
+ printfn ""
printfn "== Altering the cyphertext..."
encrypt ()
- incrementByteCipherFileAt (32L + 256L + 256L)
+ incrementByteCipherFileAt (int64 <| API.hmacSize + API.signatureSize + API.keysSize)
try
decrypt ()
assert false
- with
+ with
| error -> assert (error :? IntegrityError)
File.Delete cipherFilename
\urldef{\rngcryptoserviceprovider}\url{http://msdn.microsoft.com/en-us/library/system.security.cryptography.rngcryptoserviceprovider%28v=vs.110%29.aspx}
\urldef{\rsasecurity}\url{http://en.wikipedia.org/wiki/RSA_Security}
\urldef{\wikiml}\url{http://en.wikipedia.org/wiki/ML_%28programming_language%29}
+\urldef{\rsaxmlformat}\url{http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsa.toxmlstring%28v=vs.110%29.aspx}
\title{ICR - Labo \#2 : \textit{Conception et implémentation d'un container sécurisé pour des données médicales}}
\author{G.Burri}
\subsection{Quel est le niveau de sécurité que l'on souhaite atteindre ?}
+Le niveau souhaité est de 128 bits. Cela implique l'utilisation d'une clef \emph{AES} de 128 bits et de clefs \emph{RSA} de 3072 bits d'après~\cite{wiki-key-size}.
+
+Les éléments de sécurité suivants sont requis :
+
\begin{itemize}
\item Confidentialité : les données chiffrées ne doivent pas pouvoir être décryptées par un attaquant.
\item Authenticité : un attaquant ne doit pas pouvoir forger un container. Une signature est réalisée à l'aide d'une paire de clefs \emph{RSA} publique-privée.
\subsection{Comment s'assure-t-on que les données stockées sont authentiques ? Quels sont les risques à prendre en compte ?}
-L'empreinte des données est signée à l'aide d'une clef privée donnée en paramètre de l'\emph{API} : ceci représente la signature qui est placée dans le container. Lors du déchiffrement, la clef publique correspondante est fournie puis utilisée pour déchiffrer l'empreinte qui est comparée à l'empreinte des données.
+L'empreinte des données chiffrées est signée à l'aide d'une clef privée donnée en paramètre de l'\emph{API} : ceci représente la signature qui est placée dans le container. Lors du déchiffrement, la clef publique correspondante est fournie puis utilisée pour vérifier la signature avec l'empreinte des données chiffrées.
\subsection{Comment s'assure-t-on que les données stockées sont intègres ?}
Concerne les clefs externes à l'\emph{API}.
\begin{itemize}
- \item Une paire de clefs \emph{RSA-2048} pour la signature.
- \item Une paire de clefs \emph{RSA-2048} pour le chiffrement des clefs \emph{AES}.
+ \item Une paire de clefs \emph{RSA-3072} pour la signature.
+ \item Une paire de clefs \emph{RSA-3072} pour le chiffrement des clefs \emph{AES}.
\end{itemize}
\section{Choix des algorithmes et des paramètres}
\begin{itemize}
- \item \emph{RSA-2048} pour la signature ainsi que pour le chiffrage des clefs \emph{AES} et \emph{HMAC}. Le bourrage \emph{OAEP} (\emph{PKCS\#1 v2}) est utilisé ;
+ \item \emph{RSA-3072} pour la signature ainsi que pour le chiffrage des clefs \emph{AES} et \emph{HMAC}. Le bourrage \emph{OAEP} (\emph{PKCS\#1 v2}) est utilisé ;
\item \emph{HMAC-SHA256} pour la vérification de l'intégrité ;
\item \emph{AES-CBC128} pour le chiffrement symétrique du contenu du fichier et des méta-données associées. Le bourrage \emph{PKCS7} est utilisé.
\end{itemize}
-D'après~\cite{wiki-key-size}, la société \emph{RSA Security}\footnote{\rsasecurity} annonce qu'une taille de clefs \emph{RSA} de 2048 bits est suffisante jusqu'en 2030. Cela dépend également du niveau d'importance des documents que l'on souhaite chiffrer dans la mesure ou une attaque demande énormément de moyens.
+D'après~\cite{wiki-key-size}, la société \emph{RSA Security}\footnote{\rsasecurity} annonce qu'une taille de clefs \emph{RSA} de 3072 bits est suffisante pour une utilisation au delà de 2030. Cela dépend également du niveau d'importance des documents que l'on souhaite chiffrer dans la mesure ou une attaque demande énormément de moyens.
Toujours d'après~\cite{wiki-key-size}, une taille de clef \emph{AES} de 128 bits reste, actuellement, hors de portée de toutes attaques.
\begin{lstlisting}[frame=single, breaklines, basicstyle=\ttfamily\footnotesize]
container = header, ciphertext ;
-header = mac[256], signature[2048], keys[2048] ;
+header = mac[256], signature[3072], keys[3072] ;
ciphertext = AES(plaintext) ;
plaintext = meta-data, file-content ;
meta-data = nb-meta-data[byte], { key-value-pair } ;
\texttt{nb-meta-data} est le nombre de paires clef-valeur des méta-données.
-\texttt{keys} correspond aux clefs $k_c$ et $k_a$ ainsi qu'à l'\emph{IV}, le tout chiffré avec \emph{RSA-2048}. La taille des données chiffrées est égale à $k_c + k_a + iv = 128 + 256 + 128 = 512\,bits$.
+\texttt{keys} correspond aux clefs $k_c$ et $k_a$ ainsi qu'à l'\emph{IV}, le tout chiffré avec \emph{RSA-3072}. La taille des données à chiffrer est égale à $k_c + k_a + iv = 128 + 256 + 128 = 512\,bits$.
Les méta-données (\texttt{meta-data}) peuvent contenir, par exemple, le nom du fichier, sa date de création, ses droits, ou toutes autres données associées.
\end{lstlisting}
\end{minipage}
+Les formats des clefs, publique et privée, sont décrits sur cette page~\footnote{\rsaxmlformat}.
+
\subsection{Mesures de performance}
Taux \emph{CPU} & 1 x 100 \% & 1 x 100 \% & 1 x 100 \% & 1 x 100 \% \\
\end{tabular}
+\subsubsection{Génération de paire de clefs \emph{RSA}}
+
+La génération de clefs \emph{RSA} est très lente sous \emph{Mono}. Pour une taille de 2048 bits cela prend environ une seconde, pour une taille de 3072 bits cela prend environ dix secondes.
+
\section{Analyse de la sécurité de l'implémentation}
\subsection{Quelles sont les parties critiques du code et comment s'assure-t-on que ces parties soient correctement implémentées ?}
-Le choix des algorithmes, de leurs paramètres et de leur implémentation est une partie critique. Il est possible de se référer aux recommandations de certains organismes comme par exemple le \emph{NIST}\footnote{\emph{ National Institute of Standards and Technology}}.
+Le choix des algorithmes, de leurs paramètres et de leur implémentation est une partie critique. Il est possible de se référer aux recommandations de certains organismes comme par exemple le \emph{NIST}\footnote{\emph{National Institute of Standards and Technology}}.
La génération des clefs \emph{AES} doit être faite avec un générateur cryptographique. Dans notre cas nous utilisons \emph{RNGCryptoServiceProvider}\footnote{\rngcryptoserviceprovider}.
typedef struct {
UBYTE mac[32] <bgcolor=cLtRed>;
- UBYTE signature[256] <bgcolor=cLtBlue>;
- UBYTE key[256] <bgcolor=cLtGreen>;
+ UBYTE signature[384] <bgcolor=cLtBlue>;
+ UBYTE key[384] <bgcolor=cLtGreen>;
} Header;
Header header;