Recentment m'ha tocat barallar-me amb aquest tipus de criptografia. I realment és interessant com encaixen les diferents peces del puzle.
La motivació del PKCS#7 és que els algoritmes d'encriptació asimètrics (els que fan servir clau pública i clau privada) no poden xifrar quantitats grans de dades. Al contrari els algoritmes simètrics (la mateix clau es fa servir per encriptar que per desencriptar) són "bons" xifrant quantitats grans de dades.
Per això el que es fa és el següent: les dades es xifren amb un algoritme simètric fent servir una clau generada aleatòriament que només es fa servir en un missatge. Aquesta clau simètrica és petita i es xifra amb la clau pública d'una algoritme asimètric.
La informació arriba en Base64, que és un tipus de codificació, per la que C# ja té una forma fàcil de gestionar. Es fa amb el Convert, que retorna un array de bytes.
Aquests bytes no són ASCII, són ASN.1 (Abstract Syntax Notation 1) que és un format binari d'encapsular informació. De fet és molt similar al XML, però amb binari. Té TAGs, longitud del TAG i valor del TAG. Apart del tema del BER i el DER (però no vull explicar aquí tot l'estàndard ASN.1). El ASN.1 és antic i mig internet el fa servir (i jo sense saber-ho).
Per donar estructura a un missatge ASN.1 hi ha un llenguatge que defineix les estructures el formen. És el Encoding Control Notation (ECN), seria l'equivalent a un XSD/DTD del XML. Aleshores per rebre un missatge ASN.1 que segueixi un estàndard ha de complir una estructura ECN determinada, concretament el PKCS#7 fa servir el Cryptographic Message Syntax (RFC 5652).
Aleshores, el que defineix aquest RFC 5652 és el format en que han de posar-se les parts del missatge, es a dir:
Les dades xifrades amb l'algoritme simètric.
La clau simètrica feta servir per xifrar les dades.
El vector d'inicialització del xifrat simètric (aquest està en clar, sense xifrar).
Dades de la clau pública emprada per xifrar la clau simètrica, normalment aquesta clau està guardada en un certificat X509, aleshores en les dades s'inclou la informació típica sobre el certificat, email, dades de l'emissor del certificat (país, adreça,...), etc...
Igual que el XML posa noms a les marques, el ASN.1 té OID (Object Identifier), que identifiquen el contingut d'un tag. Aleshores cada un dels punts anteriors queda identificat dins del missatge. En format ASCII i PrettyPrint un missatge PKCS#7 pot quedar:
Per implementar tot això en C# he fet servir la llibreria BouncyCastle en la versió C#, et simplifica la vida el no tenir que batallar amb el ASN.1 ja que la llibreria m'ha permès transformar el ASN.1 a un string normal. I un cop ja tinc l'string m'he buscat la vida per desencriptar-lo amb llibreries .NET normals. He trobat altres llibreries que permeten processar el ASN.1 però cap m'ha permès obtenir el string normal i que posteriorment no hagi de parsejar-lo. Per exemple hi ha una implementació del projecte Mono que et fa un PrettyPrint del text ASN.1. També cal comentar que durant l'encriptació depenent del format en la que tenim la clau pública el ASN.1 generat té un format o un altra, no és el mateix disposar del certificat X509 o de la clau pública en format PEM.
El codi d'encriptació és força senzill. Únicament cal veure si encriptem fent servir un certificat X509 amb totes les dades o bé un fitxer PEM que conté la clau pública.
CmsEnvelopedDataGenerator gen = new CmsEnvelopedDataGenerator();
X509CertificateParser Parser = new X509CertificateParser();
StreamReader sr = new StreamReader(FitxerCertificat);
X509Certificate Cert = Parser.ReadCertificate(sr.BaseStream);
gen.AddKeyTransRecipient(Cert);
/*
byte[] KeyId = new byte[] { 1, 2, 3, 4, 5 };
TextReader TxtRead = File.OpenText(@"c:\Ruta_al_fitxer\Public.txt");
Org.BouncyCastle.OpenSsl.PemReader PubCert =
new Org.BouncyCastle.OpenSsl.PemReader(TxtRead);
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters RSAParams =
(Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)PubCert.ReadObject();
gen.AddKeyTransRecipient(RSAParams, KeyId);
*/
CmsProcessableByteArray ByteArr = new CmsProcessableByteArray(OrigenBytes);
CmsEnvelopedData Data2 = gen.Generate(ByteArr, AES256_OID_STR);
txtEncrypted.Text = Convert.ToBase64String(Data2.GetEncoded());
Per desencriptar el codi és el següent:
byte[] Base64 = Convert.FromBase64String(Origen);
/*
Codi pel Pretty Parser
ASN1Element ASN = new ASN1Element(Base64, 0);
StringBuilder SB = new StringBuilder();
ASN.BuildString(SB, 3);
string Resultat2 = SB.ToString();
PrettyPrinter P = new PrettyPrinter(ASN);
string Resultat = P.ToString();
*/
CmsEnvelopedData Data = new CmsEnvelopedData(Base64);
Content = Data.ContentInfo.Content.ToString();
string[] Splitted = Content.Split('#');
int Posicio = 0;
string ClauAESEncriptadaRSA = "";
string strIV = "";
string DadesEncriptadesAES = "";
switch(Splitted.Length)
{
//Si s'encripta amb una clau PEM sense les dades d'un certificat aleshores hi 5 parts, per que s'especifica el keyID//Queda algo com://[2, [[2, [0]#0102030405, [1.2.840.113549.1.1.1, NULL],
#88ed9a2dc3f6f520a955dda3b3460c2e72b232f0cebb20aeeb5e9a7d27e7df3d1a026b017414c61483484fb7877
d466fc8bf47b20a1a6a9b4caf314f7862ea9cdcd4c7fdea168b07748d6bd13866df1c2319c07650a4a14c697b215
982007804b49a6c7f673b928f0417d44900bedb3f97bd5c4f6a08efce136e88f7b2fa714b2c03baad24ce51a3ad0
96649393ac71af2d6bcba3d42b108b5ec9b106028fd782b29162144c01f1fbca81200006b6c1c1513929b1d8b82d
ae2ec4bd0b37d2aaa08a4f1c8901ec23ad7e4d4eda522137fec9a00d34ef25c8f5f00fbe9a3cdce83bcdede83c9d
dff7b91c0c46d6394f4150b98782c12380cccd06d06858548c872]], [1.2.840.113549.1.7.1, [2.16.840.1.101.3.4.1.42,
#dd65197a345a16990dba5e3b60653a23],
[0]#1af48e832434616b6d16e235a28690fb8b506753525e91eb0674e20cd6224e1ebe13346b6cc01cc10ecf8442
12291077750569e09986dea001beec448fb66b69c878eebae45e1303f05518705c0a0626]]case 5:
{
Posicio = Splitted[2].IndexOf(']');
ClauAESEncriptadaRSA = Splitted[2].Substring(0, Posicio);
Posicio = Splitted[3].IndexOf(']');
strIV = Splitted[3].Substring(0, Posicio);
Posicio = Splitted[4].IndexOf(']');
DadesEncriptadesAES = Splitted[4].Substring(0, Posicio);
break;
}
//Si s'encripta amb un fitxer CRT que només té la clau publica hi ha menys trossos binaris//Queda algo com://[0, [[0, [[[[1.2.840.113549.1.9.1, email@email.es]],
[[2.5.4.6, ES]], [[2.5.4.8, Barcelona]], [[2.5.4.7, Carrer A 33]],
[[2.5.4.10, La meva empresa]], [[2.5.4.11, El meu departament]], [[2.5.4.3, Projecte]]], 1444401454],
[1.2.840.113549.1.1.1, NULL],
#9a5aff081265f8a5ec74a5ec10173758c8520a74c6373455f74ce5fa35699bd036028b6999b9a5303486e95fa39da
87215e14e273639c8ad76854e9d9b3b1ef6c4d00a8f0c9b6be1c0d0f92a5298c7fcd0c4ae782ce08545d3693810655
bc7fcbfea2dd2093b8de1c7af7b930e27d72b2b4e0bb45b3624923c791bc429160b0717cc7867698f4ee991f175812
58dfd8a53b6207e482f8840ff832a721188043f849d561d4115195a664f8baacdab2d75ccb486a4107e94c8d4d3c3f
f542cb997a8c9d444a53ceb21434b48c85bbf18d45131b9d5998cbb06911537bc1f8413cf3e669d3289a42fd699cc8
8e1b0d2d5b399bf40c2b4c2aefa5967c66516cfba0c]], [1.2.840.113549.1.7.1, [2.16.840.1.101.3.4.1.42,
#6ab63f859a84a8422708ceed3a4d3244],
[0]#242d87bbf418ab8773945fedc0e38761e111827e298a72998a5e52e241b723df86e9804ef373118d4ba951b2e0
bea96f41bc6da80a5131dc8fc6d98d97d378cc25d5b6f8c2b0baf42506dd1a7bf9f9de]]case 4:
{
Posicio = Splitted[1].IndexOf(']');
ClauAESEncriptadaRSA = Splitted[1].Substring(0, Posicio);
Posicio = Splitted[2].IndexOf(']');
strIV = Splitted[2].Substring(0, Posicio);
Posicio = Splitted[3].IndexOf(']');
DadesEncriptadesAES = Splitted[3].Substring(0, Posicio);
break;
}
}
byte[] ClauAESEncriptadaRSA_bytes = ConvertHexStringToByteArray(ClauAESEncriptadaRSA);
OrigenBytes = ConvertHexStringToByteArray(DadesEncriptadesAES);
//Desencriptador de la Clau AES
RSACryptoServiceProvider KeyImporter = new RSACryptoServiceProvider();
//Carreguem la clau
KeyImporter.LoadPrivateKeyPEM(PrivateKeyPEM);
//Obtenim la clau AESbyte[] PrivateKey = KeyImporter.Decrypt(ClauAESEncriptadaRSA_bytes, false);
AesCryptoServiceProvider Decrypter = new AesCryptoServiceProvider();
byte[] IV = ConvertHexStringToByteArray(strIV);
ICryptoTransform EncDec = null;
Decrypter.Key = PrivateKey;
Decrypter.IV = IV;
Decrypter.Mode = CipherMode.CBC;
Decrypter.Padding = PaddingMode.None;
EncDec = Decrypter.CreateDecryptor();
DecryptedBytes = newbyte[OrigenBytes.Length];
for (int i = 0; i < OrigenBytes.Length; i = i + 16) EncDec.TransformBlock(OrigenBytes, i, 16, DecryptedBytes, i);
if (chkCopyToClipboard.Checked) Clipboard.SetText(txtDecrypt.Text);
txtBase64.Text = Content;
txtDecrypt.Text = ConvertByteArrayASCIIToString(DecryptedBytes);
txtDecrypt.Text = txtDecrypt.Text.Replace("[03]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[04]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[05]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[06]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[07]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[08]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[09]", "");
#02/11/2015 10:22 Programació C# Autor: Alex Canalda