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

<?php
$pub = <<<SOMEDATA777
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBalqbherlcyodykc5sdlqi49arygyg1mq
aH9/GnWjGavZM02fos4lc2w6tCchcUBNtJvGqkwhc5jenx3ryosx2uccaweaaq==
-----END PUBLIC KEY-----
SOMEDATA777;
$data = "PHP is my secret love.";
$pk = openssl_get_publickey($pub);
openssl_public_encrypt($data, $encrypted, $pk);
echo chunk_split(base64_encode($encrypted));
?>

goo.gl/Xb7ayw

Одержимо, що-то типу (ви будете отримувати новий унікальний шифротекст при кожній спробі зашифрувати текст):

JutBa0GLHzGrlygxwWr66cizw4W4za+DbzZweNM0iloCD7xEP9LclL013lcksJL5xhjw44u+oxpq
cX1ZSLhWuA==

decode.php

<?php
$key = <<<SOMEDATA777
-----BEGIN RSA PRIVATE KEY-----
MIIBPQIBAAJBALqbHeRLCyOdykC5SDLqi49arygyg1mqah9/GnWjGavZM02fos4l
c2w6tCchcUBNtJvGqKwhC5JEnx3RYoSX2uccaweaaqjbakn6o+tFFDt4MtBsNcDz
GDsYDjQbCubNW+yvKbn4PJ0UZoEebwmvH1ouKaUuacJcsiqkkzthleu4krygugo1
mEECIQD0dUhj71vb1rN1pmTOhQOGB9GN1mygcxaifoww8znlrwihamnqlflijus6
rY+h1pJa/3Fh1HTSOCCCCWA0NRFnMANhAiEAwddKGqxpo6goz26s2rhqlhqyr47k
vgPkZu2jDCo7trsCIQC/PSfRsnSkEqCX18GtKPCjfSH10WSsK5YRway3kcylaqih
AL70wdUu5jMm2ex5cZGkZLRB50yE6rBihcd5w1wdtfoe
-----END RSA PRIVATE KEY-----
SOMEDATA777;
$data = "JutBa0GLHzGrlygxwWr66cizw4W4za+DbzZweNM0iloCD7xEP9LclL013lcksJL5xhjw44u+oxpq cX1ZSLhWuA==";
$pk = openssl_get_privatekey($key);
openssl_private_decrypt(base64_decode($data), $out, $pk);
echo $out;
?>

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 int LEN = 76
for len(s1) > 76 {
s2 = s2 + s1[:LEN] + "\n"
s1 = s1[LEN:]
}
s2 = s2 + s1
return s2
}

func Base64Dec(string s1) ([]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/nsyAw5kYDt

Go 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.html

Android не 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 (0x10001)
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 ключ.

Всім весни і удачі.
Ну як?

/>
/>


<input type=«radio» id=«vv66387»
class=«radio js-field-data»
name=«variant[]»
value=«66387» />
я втомився це читати
<input type=«radio» id=«vv66389»
class=«radio js-field-data»
name=«variant[]»
value=«66389» />
я втомився писати

Проголосувало 7 осіб. Утрималося 5 чоловік.


Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.