RSA шифрование в PHP (openssl), Android/Java, JavaScript и Go
RSA — это алгоритм шифрования с открытым ключем. Шифрование с открытым ключем весьма полезная вещь. RSA позволяет создать два ключа: открытый и закрытый. Разместить открытый ключ где-то и им шифровать, а расшифровать сможет только обладатель закрытого ключа.Например, мы можем сделать веб магазин на ПХП, который будет принимать заказы с данными кредитных карт. Магазин на ПХП будет шифровать данные кредитных карт открытым ключем. Сам пхп-магазин расшифровать эти зашифрованные данные уже не сможет. Хорошее решение, хакер неожиданно так взломает веб магазин (написанный на ПХП), а карты зашифрованы.Но как же владелец сайта будет получать доступ к картам? Ему нужно взять шифротекст, закрытый ключ и расшифровать. Можно представить, что закрытый ключ будет храниться в телефоне, и телефон может вытянуть шифротекст с админки через QR код. Но в разных языках реализация криптографии немного отличается, и моя статья как раз о том, как зашифровать текст на одном языке програмирования, а расшифровать на другом языке.
В чем могут быть отличия? — как хранятся ключи; — как хранится шифротекст: бинарная форма или в кодировке base64; — паддинг.
Первым делом нам нужны ключи. Я предлагаю их создать с помощью openssl
openssl genrsa -out private.pem 512 openssl rsa -in private.pem -out public.pem -outform PEM -pubout Для экономии места на экране я выбрал 512 бит, целесообразно использовать 1024 или 2048 бит. Например, SSL gitgub.com использует 2048.
Так же размер ключа определяет максимальный объем данных, которые вы можете зашифровать, но с учетом того, что мы будем использовать OPENSSL_PKCS1_PADDING (по умолчанию в ПХП), то из размера ключа следует вычесть 11 байт и при ключе 512 бит мы можем зашифровать 53 байта. Не использовать паддинг вообще опасно, если вы не знаете зачем он нужен.
Теперь у нас есть private.pem и public.pem. Эти ключи в текстовом формате, и их будет достаточно удобно использовать в примерах. Я хочу, чтобы каждая программа состояла из одного файла, так будет наглядней.
private.pem -----BEGIN RSA PRIVATE KEY----- MIIBPQIBAAJBALqbHeRLCyOdykC5SDLqI49ArYGYG1mqaH9/GnWjGavZM02fos4l c2w6tCchcUBNtJvGqKwhC5JEnx3RYoSX2ucCAwEAAQJBAKn6O+tFFDt4MtBsNcDz GDsYDjQbCubNW+yvKbn4PJ0UZoEebwmvH1ouKaUuacJcsiQkKzTHleu4krYGUGO1 mEECIQD0dUhj71vb1rN1pmTOhQOGB9GN1mygcxaIFOWW8znLRwIhAMNqlfLijUs6 rY+h1pJa/3Fh1HTSOCCCCWA0NRFnMANhAiEAwddKGqxPO6goz26s2rHQlHQYr47K vgPkZu2jDCo7trsCIQC/PSfRsnSkEqCX18GtKPCjfSH10WSsK5YRWAY3KcyLAQIh AL70wdUu5jMm2ex5cZGkZLRB50yE6rBiHCd5W1WdTFoe -----END RSA PRIVATE KEY----- public.pem -----BEGIN PUBLIC KEY----- MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALqbHeRLCyOdykC5SDLqI49ArYGYG1mq aH9/GnWjGavZM02fos4lc2w6tCchcUBNtJvGqKwhC5JEnx3RYoSX2ucCAwEAAQ== -----END PUBLIC KEY----- Начнем с ПХП encode.php goo.gl/Xb7aywПолучим, что-то типа (вы будете получать новый уникальный шифротекст при каждой попытке зашифровать текст):
JutBa0GLHzGrlygxwWr66cizw4W4za+DbzZweNM0iloCD7xEP9LclL013lcksJL5XhjW44U+oxpq cX1ZSLhWuA== decode.php goo.gl/0CWTQ9Теперь на Go package main
import ( «crypto/rand» «crypto/rsa» «crypto/x509» «encoding/base64» «encoding/pem» «errors» «fmt» «strings» )
func main () { b64:= `JutBa0GLHzGrlygxwWr66cizw4W4za+DbzZweNM0iloCD7xEP9LclL013lcksJL5XhjW44U+oxpq cX1ZSLhWuA== ` b1, err:= Base64Dec (b64) if err!= nil { panic (err) } b2, err:= RsaDecrypt (b1, privateKey) fmt.Println (string (b2), err)
b1, err = RsaEncrypt ([]byte («Go the best language»), publicKey) if err!= nil { panic (err) } s1:= Base64Enc (b1) fmt.Println (s1) b1, err = Base64Dec (s1) b2, err = RsaDecrypt (b1, privateKey) fmt.Println (string (b2), err)
}
func Base64Enc (b1 []byte) string { s1:= base64.StdEncoding.EncodeToString (b1) s2:= » var LEN int = 76 for len (s1) > 76 { s2 = s2 + s1[: LEN] + »\n» s1 = s1[LEN:] } s2 = s2 + s1 return s2 }
func Base64Dec (s1 string) ([]byte, error) { s1 = strings.Replace (s1,»\n»,», -1) s1 = strings.Replace (s1,»\r»,», -1) s1 = strings.Replace (s1,» »,», -1) return base64.StdEncoding.DecodeString (s1) }
func RsaDecrypt (ciphertext []byte, key []byte) ([]byte, error) { block, _ := pem.Decode (key) if block == nil { return nil, errors.New («private key error!») } priv, err:= x509.ParsePKCS1PrivateKey (block.Bytes) if err!= nil { return nil, err } return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext) }
func RsaEncrypt (origData []byte, key []byte) ([]byte, error) { block, _ := pem.Decode (key) if block == nil { return nil, errors.New («public key error») } pubInterface, err:= x509.ParsePKIXPublicKey (block.Bytes) if err!= nil { return nil, err } pub:= pubInterface.(*rsa.PublicKey) return rsa.EncryptPKCS1v15(rand.Reader, pub, origData) }
var publicKey = []byte (` -----BEGIN PUBLIC KEY----- MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALqbHeRLCyOdykC5SDLqI49ArYGYG1mq aH9/GnWjGavZM02fos4lc2w6tCchcUBNtJvGqKwhC5JEnx3RYoSX2ucCAwEAAQ== -----END PUBLIC KEY----- `)
var privateKey = []byte (` -----BEGIN RSA PRIVATE KEY----- MIIBPQIBAAJBALqbHeRLCyOdykC5SDLqI49ArYGYG1mqaH9/GnWjGavZM02fos4l c2w6tCchcUBNtJvGqKwhC5JEnx3RYoSX2ucCAwEAAQJBAKn6O+tFFDt4MtBsNcDz GDsYDjQbCubNW+yvKbn4PJ0UZoEebwmvH1ouKaUuacJcsiQkKzTHleu4krYGUGO1 mEECIQD0dUhj71vb1rN1pmTOhQOGB9GN1mygcxaIFOWW8znLRwIhAMNqlfLijUs6 rY+h1pJa/3Fh1HTSOCCCCWA0NRFnMANhAiEAwddKGqxPO6goz26s2rHQlHQYr47K vgPkZu2jDCo7trsCIQC/PSfRsnSkEqCX18GtKPCjfSH10WSsK5YRWAY3KcyLAQIh AL70wdUu5jMm2ex5cZGkZLRB50yE6rBiHCd5W1WdTFoe -----END RSA PRIVATE KEY----- `)
play.golang.org/p/nsyAw5kYDtGo Playground всегда дают одни и те же случайные числа и поэтому результат:
aOleRSXhBT1XR7Al9cxdmM/8KnM2CvQdnNqnvwtq1ivFJ1aITxJUCuTw8ZRB8mY+elhoiUmC4UjM mwyTKmjqQw== JavaScript шифрование Шифровать я буду с помощью jsEncrypt: $(function () { $('#but').click (function (){ var pub = $('#pub').val (); var crypt = new JSEncrypt (); crypt.setPublicKey (pub); var data = $('#data').val (); $('#out').val (crypt.encrypt (data)); }); });
cossackpyra.github.io/april14/html/encrypt.htmlИ получил:
C2uWXwp6OsxLKnr3cXpJIf/RcPzgjlxNXj8IX2R47binEo2dLFhJISDnOioQaM8kAl/lqSSOCLdrYP12Tc/YXQ== $(function () { $('#but').click (function (){ var key = $('#key').val (); var crypt = new JSEncrypt (); crypt.setPrivateKey (key); var data = $('#data').val (); $('#out').val (crypt.decrypt (data)); }); }); cossackpyra.github.io/april14/html/decrypt.htmlAndroid не Java Java — это так много всего, и ни фига нет.В Android есть android.util.Base64, а Java 8 java.util.Base64, а еще есть org.apache.commons.codec.binary.Base64.
Java не умеет читать сертификаты в PEM формате, какие задачи и цели ставило руководство перед создателями java.security и javax.crypto — это мрак, но явно не экономить место на диске.
В Bouncy Castle есть PEMParser. Но Bouncy Castle в онлайн редактор не подцепишь, а в Android используется непонятно какой Bouncy Castle. Поэтому есть Spongy Castle, но это уже будет другая графа в реализации криптографии в форме «Supplement No. 5 to Part 742—Encryption Registration» на пункты 6 и 7 уже не ответишь «нет, нет».
SNAP-R (6) Do the products incorporate encryption components produced or furnished by non-U.S. sources or vendors? (If unsure, please explain.)No.
(7) With respect to your company’s encryption products, are any of them manufactured outside the United States? If yes, provide manufacturing locations. (Insert «not applicable», if you are not the principal producer of encryption products.)
No.
Поэтому из private.pem можно вынуть модуль, приватную и публичную экспоненты. (Это допустимо, я изначально ключи создал с помощью openssl.)
openssl rsa -in private.pem -text -noout Private-Key: (512 bit) modulus: 00: ba:9b:1d: e4:4b:0b:23:9d: ca:40: b9:48:32: ea: 23:8f:40: ad:81:98:1b:59: aa:68:7f:7f:1a:75: a3: 19: ab: d9:33:4d:9f: a2: ce:25:73:6c:3a: b4:27:21: 71:40:4d: b4:9b: c6: a8: ac:21:0b:92:44:9f:1d: d1: 62:84:97: da: e7 publicExponent: 65537 (0×10001) privateExponent: 00: a9: fa:3b: eb:45:14:3b:78:32: d0:6c:35: c0: f3: 18:3b:18:0e:34:1b:0a: e6: cd:5b: ec: af:29: b9: f8: 3c:9d:14:66:81:1e:6f:09: af:1f:5a:2e:29: a5:2e: 69: c2:5c: b2:24:24:2b:34: c7:95: eb: b8:92: b6:06: 50:63: b5:98:41 prime1: 00: f4:75:48:63: ef:5b: db: d6: b3:75: a6:64: ce:85: 03:86:07: d1:8d: d6:6c: a0:73:16:88:14: e5:96: f3: 39: cb:47 prime2: 00: c3:6a:95: f2: e2:8d:4b:3a: ad:8f: a1: d6:92:5a: ff:71:61: d4:74: d2:38:20:82:09:60:34:35:11:67: 30:03:61 exponent1: 00: c1: d7:4a:1a: ac:4f:3b: a8:28: cf:6e: ac: da: b1: d0:94:74:18: af:8e: ca: be:03: e4:66: ed: a3:0c:2a: 3b: b6: bb exponent2: 00: bf:3d:27: d1: b2:74: a4:12: a0:97: d7: c1: ad:28: f0: a3:7d:21: f5: d1:64: ac:2b:96:11:58:06:37:29: cc:8b:01 coefficient: 00: be: f4: c1: d5:2e: e6:33:26: d9: ec:79:71:91: a4: 64: b4:41: e7:4c:84: ea: b0:62:1c:27:79:5b:55:9d: 4c:5a:1e
В Java это будет выглядеть так: // Private key BigInteger modulus = new BigInteger ( «BA9B1DE44B0B239DCA40B94832EA238F40AD81981B59AA687F7F1A75A319ABD9334D9FA2CE25736C3AB4272171404DB49BC6A8AC210B92449F1DD1628497DAE7», 16); BigInteger exp = new BigInteger ( »00a9fa3beb45143b7832d06c35c0f3183b180e341b0ae6cd5becaf29b9f83c9d1466811e6f09af1f5a2e29a52e69c25cb224242b34c795ebb892b6065063b59841», 16);
//Public Key
BigInteger modulus = new BigInteger ( «BA9B1DE44B0B239DCA40B94832EA238F40AD81981B59AA687F7F1A75A319ABD9334D9FA2CE25736C3AB4272171404DB49BC6A8AC210B92449F1DD1628497DAE7», 16); BigInteger pubExp = new BigInteger (»010001», 16);
Вся программа на Java 8: import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher;
import java.util.Base64;
//import javax.xml.bind.DatatypeConverter;
public class HelloWorld {
public static void main (String[] args) throws Exception { try { byte[] b1 = decrypt («JutBa0GLHzGrlygxwWr66cizw4W4za+DbzZweNM0iloCD7xEP9LclL013lcksJL5XhjW44\nU+oxpqcX1ZSLhWuA==»); String s1 = new String (b1, «UTF-8»); System.out.println (s1); byte[] b2 = encrypt («Java kills».getBytes («UTF-8»)); String s2 = Base64.getEncoder ().encodeToString (b2); System.out.println (s2); byte[] b3 = decrypt (s2); String s3 = new String (b3, «UTF-8»); System.out.println (s3);
} catch (UnsupportedEncodingException e) { e.printStackTrace (); } catch (Exception e) { e.printStackTrace (); } }
public static byte[] decrypt (String key) throws Exception {
BigInteger modulus = new BigInteger ( «BA9B1DE44B0B239DCA40B94832EA238F40AD81981B59AA687F7F1A75A319ABD9334D9FA2CE25736C3AB4272171404DB49BC6A8AC210B92449F1DD1628497DAE7», 16); BigInteger exp = new BigInteger ( »00a9fa3beb45143b7832d06c35c0f3183b180e341b0ae6cd5becaf29b9f83c9d1466811e6f09af1f5a2e29a52e69c25cb224242b34c795ebb892b6065063b59841», 16);
RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec (modulus, exp); KeyFactory kf = KeyFactory.getInstance («RSA»); PrivateKey privKey = kf.generatePrivate (keySpec);
Cipher cipher = Cipher.getInstance («RSA/ECB/PKCS1Padding»); cipher.init (Cipher.DECRYPT_MODE, privKey);
byte[] decodedStr = Base64.getDecoder ().decode ( key.replace (»\n»,»).replace (»\r»,»).replace (» »,»)); byte[] plainText = cipher.doFinal (decodedStr);
return plainText; }
private static byte[] encrypt (byte[] b1) throws Exception { BigInteger modulus = new BigInteger ( «BA9B1DE44B0B239DCA40B94832EA238F40AD81981B59AA687F7F1A75A319ABD9334D9FA2CE25736C3AB4272171404DB49BC6A8AC210B92449F1DD1628497DAE7», 16); BigInteger pubExp = new BigInteger (»010001», 16);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec (modulus, pubExp); KeyFactory kf = KeyFactory.getInstance («RSA»); PublicKey publicKey = kf.generatePublic (keySpec);
Cipher cipher = Cipher.getInstance («RSA/ECB/PKCS1Padding»); cipher.init (Cipher.ENCRYPT_MODE, publicKey);
// byte[] decodedStr = Base64.decode (key, Base64.DEFAULT); byte[] plainText = cipher.doFinal (b1);
return plainText; } }
goo.gl/t27IWw (Надо нажать Compile, Execute)
ik1Dvev7AffP+mOgxkbnYmpZrN9nGCKEzwCA4qsADcSKZFDYC/32B4uzUNSH8D+yCjBbrE5HUDL6vs6W5idG6Q== Android программа import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec;
import javax.crypto.Cipher;
import android.util.Base64; import android.util.Log;
public class TestX {
public static byte[] decrypt (String key) throws Exception {
BigInteger modulus = new BigInteger ( «BA9B1DE44B0B239DCA40B94832EA238F40AD81981B59AA687F7F1A75A319ABD9334D9FA2CE25736C3AB4272171404DB49BC6A8AC210B92449F1DD1628497DAE7», 16); BigInteger exp = new BigInteger ( »00a9fa3beb45143b7832d06c35c0f3183b180e341b0ae6cd5becaf29b9f83c9d1466811e6f09af1f5a2e29a52e69c25cb224242b34c795ebb892b6065063b59841», 16);
RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec (modulus, exp); KeyFactory kf = KeyFactory.getInstance («RSA»); PrivateKey privKey = kf.generatePrivate (keySpec);
Cipher cipher = Cipher.getInstance («RSA/ECB/PKCS1Padding»); cipher.init (Cipher.DECRYPT_MODE, privKey);
byte[] decodedStr = Base64.decode (key, Base64.DEFAULT); byte[] plainText = cipher.doFinal (decodedStr);
return plainText; }
public static void test () { try { byte[] b1 = decrypt («JutBa0GLHzGrlygxwWr66cizw4W4za+DbzZweNM0iloCD7xEP9LclL013lcksJL5XhjW44U+oxpq\ncX1ZSLhWuA==»); String s1 = new String (b1, «UTF-8»); Log.i («TEST», s1); byte[] b2 = encrypt («Java kills».getBytes («UTF-8»)); String s2 = Base64.encodeToString (b2, Base64.CRLF); Log.i («TEST», s2); byte[] b3 = decrypt (s2); String s3 = new String (b3, «UTF-8»); Log.i («TEST», s3);
} catch (UnsupportedEncodingException e) { e.printStackTrace (); } catch (Exception e) { e.printStackTrace (); } }
private static byte[] encrypt (byte[] b1) throws Exception { BigInteger modulus = new BigInteger ( «BA9B1DE44B0B239DCA40B94832EA238F40AD81981B59AA687F7F1A75A319ABD9334D9FA2CE25736C3AB4272171404DB49BC6A8AC210B92449F1DD1628497DAE7», 16); BigInteger pubExp = new BigInteger (»010001», 16);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec (modulus, pubExp); KeyFactory kf = KeyFactory.getInstance («RSA»); PublicKey publicKey = kf.generatePublic (keySpec);
Cipher cipher = Cipher.getInstance («RSA/ECB/PKCS1Padding»); cipher.init (Cipher.ENCRYPT_MODE, publicKey);
byte[] plainText = cipher.doFinal (b1);
return plainText; } }
КО: шифровать можно не текст, а AES ключ.
Всем весны и удачи.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.