загрузка...
загрузка...
загрузка...

Детализация по запросу

загрузка...
загрузка...

Блог о пеленгации

Тема поста:

Использование библиотеки Bouncy Castle в JavaMe (Часть 2)

Предисловие.
Всем привет. Было бы классно если бы у блогопостов отображалась дата написания. То вы бы смогли обнаружить, что от текущего поста до предыдущего, несколько лет). Ну ничего нет же страшного, как говорится "лучше поздно чем никогда", да и сайт надо пополнять контентом, а то что-то на нем тишина "стоит".
Начало.
Был холодный 2009 javaMe гуляла по миру и только у некоторых групп людей на слуху были слова android, iphone, wp7. А паблик народ знал в мире мобильного develop такие понятия как:
  • java мидлет - мировая виртуальная машина - просто время шло и она дала путь новым платформам, но сама тянуть все аппаратные тяжбы не могла в одиночку с девайсом, и далее она стала просто как надстройка для мобильных ОС.
  • симбиан - просто потом с годами купила msf и подмяла под себя, а в той ОС много что было так хорошо продуманно.
  • windows mobile - у данной фирмы много денег поэтому всякую дребедень можно выпускать для галочки, что типа мы там есть на рынке мобильных девайсов.
  • blackberry - хорошая самобытная ос система, почему потом её разваливать начали непонятно, наверно суженный круг пользования был ИЛИ мало разработчиков ИЛИ просто хватались за все что лежало рядом, а разработчиков то в той команде мало было ну ИЛИ просто не поспели за прогрессом в виду последнего упомянутого факта.
Ну ладно не будем как девочки сентименталить и продолжим повествование. Тогда я работал в фирме, связанной с финансовым процессингом. Первая серьезная задача, после окончания вуза и уже 1 года работы, именно здесь была мне поставлена. Ну как сказать поставлена, просто поставили перед фактом) и надо это сделать.
Смысл был в следующем:
  1. Уже существовал механизм обмена по шифрованному каналу между сервером и клиентом.
  2. Клиентом - были платежные терминалы на версиях windows.
  3. Сервер - был написан на c++.
  4. Обмен - по протоколу ssl и введена ЭЦП по формату MSDN.
  5. И никто не хотел какому-то новенькому программисту писать никаких новых механизмов по обмену с мобильным терминалом, тк это же не актуально было тогда!))
Дано:
Работник, который:
  1. 0,5 года знает мобильную java.
  2. Получил специальность "Информационные системы в управлении", хотя раньше он думал, что получил специальность "Инженер-системотехник", спасибо одному человеку), что поправил меня)).
  3. Знания о шифровании были получены только при создании диплома, который он сам лично делал и который реально работал в организации.
Исходя из своего маленького опыта быстро только решилась проблема с протоколом ssl. Надо было просто на сервер поставить сертификат SSL от организации: Thawte или VeriSign, а на мобильном - при разработке использовать интерфейс HttpsConnection:

HttpsConnection hConnect = (HttpsConnection) Connector.open("https://WWW.DOMAIN.ORG:PORT_SSL/", Connector.READ_WRITE, true);

Зачем только ограниченный круг фирм-создателей сертификатов можно было ставить на сервер?
Кто раньше писал под javaMe помнят, что при использовании ssl протокола на некоторых телефонах спрашивалось мол соединение недоверенное продолжить да/нет, а на некоторых - просто соединение закрывалось. Проблема решалась 2 путями:
  1. Сертификацией ssl сервера от организации Thawte или VeriSign, тк в мобильных устройства часто (практически 100%) корневыми сертификатами безопасности являлись именно сертификаты от этих контор.
  2. Портирование в качестве корневого сертификата в устройство, такого же какое стоит на сервере. Но это как вы понимаете не на широкую публику.
Проблема с шифрованием канала передачи была решана быстро.
Далее проверка аутентичности сторон при обмене или проще говоря ЭЦП. В детальном рассмотрении это должно было так выглядеть.
  1. Мобильный терминал генерирует пару RSA ключей.
  2. Далее терминал инициирует обмен публичными ключами с сервером. Подготавливает свой публичный ключ для сервера (чтобы сервер смог распознать).
  3. Сервер на запрос шлет свой публичный ключ. Тут мы его парсим и приводим в удобно читаемый вид в нашем яве коде.
  4. Мобильный терминал сохраняет серверный ключ.
  5. При последующих обменах с сервером. Идет подпись сообщения приватным ключом клиента. При ответе от сервера идет верификация сообщения публичным ключом сервера.
Первый пункт быстро решился, благодаря библиотеке, о которой идет повествование, если вы забыли) это Bouncy Castle.
  
/********......*********/		
private RSAPrivateCrtKeyParameters RSAprivKey = null;
private RSAKeyParameters RSApubKey = null;
private AsymmetricCipherKeyPair keyPair = null;

public void generateRSAKeyPair() throws Exception {
    //генерация ключей для ЭЦП
    SecureRandom sr = new SecureRandom();
    BigInteger pubExp = new BigInteger("10001", 16);
    RSAKeyPairGenerator RSAKeyPairGen = new RSAKeyPairGenerator();
    RSAKeyGenerationParameters RSAKeyGenPara = new RSAKeyGenerationParameters(pubExp, sr, 1024, 80);//80
    RSAKeyPairGen.init(RSAKeyGenPara);
    keyPair = RSAKeyPairGen.generateKeyPair();
    RSAprivKey = (RSAPrivateCrtKeyParameters) keyPair.getPrivate();
    RSApubKey = (RSAKeyParameters) keyPair.getPublic();
    if (RSApubKey.getModulus().bitLength() < 1018) {
        System.out.println("RSA: failed key generation (1018) length test");
        throw new Exception("RSA: failed key generation (1018) length test");
    }
}
/********......*********/	
Любопытный факт, что тогда были такие слабенькие телефоны. К примеру на Sony Ericsson w660, процесс генерации пары RSA ключей занимал ~105 секунд. То есть стандартно эту операцию надо было помещать в поток...

Полученный приватный ключ надо сохранить, чтобы потом его использовать при следущих обменах. Но чтобы его полноценно потом восстановить из сохранения, необходимо сохранить несколько параметров:
...RSAprivKey.getModulus().toByteArray() - модуль
...RSAprivKey.getDP().toByteArray() - параметр DP RSA алгоритма
...RSAprivKey.getP().toByteArray() - случайное простое число p
...RSAprivKey.getQ().toByteArray() - случайное простое число q
...RSAprivKey.getExponent().toByteArray() - экспонента
...RSAprivKey.getDQ().toByteArray() - параметр DQ RSA алгоритма
...RSAprivKey.getPublicExponent().toByteArray() - открытая экспонента
...RSAprivKey.getQInv().toByteArray() - параметр InverseQ RSA алгоритма

то есть далее при восстановлении приватного ключа используется конструктор:
  
RSAprivKey = new RSAPrivateCrtKeyParameters (RSAmod, RSApubExp, RSAprivExp, RSAp, RSAq, RSAdp, RSAdq, RSAqInv);
	
На втором пункте сетевая составляющая как думаю всем понятна. Нас больше интересует конвертирование НАШЕГО публичного ключа в формат Public Key BLOBs, чтобы сервер понял нас и начал с "нами работать".

Формат Public Key BLOBs:
Название поляРазмерОписание
Тип1 байтPUBLICKEYBLOB (0x6)
Версия1 байтCUR_BLOB_VERSION (0x2)
Зарезервировано2 байтаPUBLICKEYBLOB (0x0000)
Идентификатор алгоритма4 байтаCALG_RSA_SIGN (0x00002400)
Магическое слово4 байта"RSA1" (0x31415352)
Длина публичной экспоненты4 байта(0x00000400)
Публичная экспонента4 байтаRAW bytes public exponents
ModulusRSAPUBKEY.bitlen/8Сам открытый ключ

Не буду вдаваться в подробности названия, тк в приведенной таблице каждая группа байт полей еще называется своим именем.

Перед подготовкой конвертирования не забываем, что byte-order java (big-endian) и си (little-endian) разный.
  
public static byte[] GetPublicRSAKeyForCryptoAPI() throws IOException, ArrayIndexOutOfBoundsException, Exception {
    if (PubKeyClient == null) { throw new IOException("Ключи ЭЦП не найдены!"); } // локальная переменная класса, отвечающая за публичный ключ устройства.
    byte PUBLICKEYBLOB = 0x06;
    byte CUR_BLOB_VERSION = 0x02;
    short RESERVED = 0x0000;
    //int CALG_RSA_SIGN = 0x00002400;
    //String MAGIC = "RSA1"; // 0x31415352
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream daos = new DataOutputStream(baos);
    try {
        //LITTLE ENDIAN!!!!!!!!!!!!!
        daos.writeByte(PUBLICKEYBLOB);
        daos.writeByte(CUR_BLOB_VERSION);
        daos.writeShort(RESERVED);
        //CALG_RSA_SIGN
        daos.writeByte(0x00);
        daos.writeByte(0x24);
        daos.writeByte(0x00);
        daos.writeByte(0x00);
        //RSA1
        daos.writeByte(0x52);
        daos.writeByte(0x53);
        daos.writeByte(0x41);
        daos.writeByte(0x31);
        //Len bit public exponent
        daos.writeByte(0x00);
        daos.writeByte(0x04);//04
        daos.writeByte(0x00);
        daos.writeByte(0x00);
        //Берем наш открытый ключ
        RSAKeyParameters kea = (RSAKeyParameters) PubKeyClient;
        //не забываем про байт ордер
        byte[] bbb = BigIntegers.asUnsignedByteArray(kea.getExponent());
        daos.write(1 > bbb.length ? 0x00 : bbb[0]);
        daos.write(2 > bbb.length ? 0x00 : bbb[1]);
        daos.write(3 > bbb.length ? 0x00 : bbb[2]);
        daos.write(4 > bbb.length ? 0x00 : bbb[3]);
        //Формируем беззнаковый массив байтов
        byte[] binp = BigIntegers.asUnsignedByteArray(kea.getModulus());
	//Содержимое открытого ключа переворачиваем
	daos.write(XDSF.ToReserveEndianByteArray(binp));
        byte rez[] = baos.toByteArray();
        return rez;
    } catch (IOException ioe) {
        throw new IOException("Error write binary data");
    } catch (ArrayIndexOutOfBoundsException ai) {
        throw new ArrayIndexOutOfBoundsException("Index out range");
    } catch (Exception e) {
        throw new Exception("Error sys...");
    } finally {
        daos.close();
        baos.close();
    }
}
Далее уже на третьем пункте нам проще. Тк надо просто напросто было сделать обратную операцию с ключом, который мы получаем от сервера.
  
private void ReadRSAPublicKeyFromCryptoAPI(byte[] CryptoApiRSA) throws Exception {
        byte PUBLICKEYBLOB = 0x06;
        byte CUR_BLOB_VERSION = 0x02;
        short RESERVED = 0x0000;
        int CALG_RSA_KEYX = 0x0000a400;
        int CALG_RSA_SIGN = 0x00002400;
        String MAGIC = "RSA1"; // 0x31415352
        DataInputStream dis = null;
        int jint = 0; // int to build Java int from little−endian ordered byte data
        int bitlen = 0;
        int pubexp = 0;
        ByteArrayInputStream bis = new ByteArrayInputStream(CryptoApiRSA);
        dis = new DataInputStream(bis);
        try {
            if (dis.readByte() != PUBLICKEYBLOB || dis.readByte() !=
                    CUR_BLOB_VERSION || dis.readShort() != RESERVED) {
                throw new Exception("Server key is not public structure!");
            }
            jint = 0;
            for (int i = 0; i < 4; i++) {
                jint += dis.readUnsignedByte() * (int) pow(256, i);
            }
            if (jint != CALG_RSA_KEYX && jint != CALG_RSA_SIGN) {
                throw new Exception("Format key is not for sign or swaping data!");
            }
            //−−−−−− Read the RSAPUBKEY struct members −−−−−−−−−//
            StringBuffer magic = new StringBuffer(4);
            for (int i = 1; i <= 4; i++) {
                magic.append((char) dis.readByte());
            }
            if (!magic.toString().equals(MAGIC)) {
                throw new Exception("Format key is not RSA1!");
            }
            for (int i = 0; i < 4; i++) {
                bitlen += dis.readUnsignedByte() * (int) pow(256, i);
            }
            for (int i = 0; i < 4; i++) {
                pubexp += dis.readUnsignedByte() * (int) pow(256, i);
            }
            int keysize = bitlen;
            //−−−−− Finally, get the modulus data, and reverse bytes to get big−endian value −−−−−−−−//
            byte[] modulus = new byte[bitlen / 8]; //should be this many bytes left
            int modbytes = dis.read(modulus);
            if (modbytes != (bitlen / 8)) {
                throw new Exception("CRC public server key is not == :)) !");
            }
            modulus = XDSF.ToReserveEndianByteArray(modulus); //reverse bytes to put in big−endian order
            PubKeyServ = new RSAKeyParameters(false, new BigInteger(1, modulus), BigInteger.valueOf(pubexp));//заполняем нашу локальную переменную класс, отвечающую за публичный ключ сервера.
        } catch (IOException o) {
            throw new Exception("Error perform getServerKey: " + o.getMessage());
        }
    }
Далее приведу код вспомогательных функций, которые использовались в прямом и обратном преобразованиях.
Возведение в степень
  
private int pow(int x, int y) {
	int r = 1;
	for (int i = 0; i < y; i++) {
		r *= x;
	}
	return r;
}
Изменение порядка байтов
  
public static byte[] ToReserveEndianByteArray(byte[] mas) {
        byte m[] = new byte[mas.length];
        for (int i = 0; i < mas.length; i++) {
            m[i] = mas[mas.length - i - 1];
        }
        return m;
    }
Остальные функции Вы можете найти в самой рассматриваемой библиотеке Bouncy Castle.
Ну и на конец четвертый шаг. Это подпись и верификация сообщений.

Подпись сообщения приватным ключом устройства
  
 public static byte[] SignRSA(byte[] DataSign) throws Exception {
        if (PrivKeyClient == null) {//локальная переменная "Закрытый ключ устройства"
            throw new Exception("Ключи ЭЦП не найдены!");
        }
        SHA1Digest dig = new SHA1Digest();
        RSADigestSigner signer = new RSADigestSigner(dig);
        CipherParameters cipa = (CipherParameters) PrivKeyClient;
        signer.init(true, cipa);
        signer.update(DataSign, 0, DataSign.length);
        byte[] sign = signer.generateSignature();
        BigInteger bi_sign = new BigInteger(XDSF.ToReserveEndianByteArray(sign));
        signer.reset();
        return BigIntegers.asUnsignedByteArray(bi_sign);
    }
Верификация принятого сообщения серверным публичным ключом
  
public static boolean RSAVerifyServ(byte[] mesg, byte[] sig) throws Exception {
        if (PubKeyServ == null) {//локальная переменная "Публичный ключ сервера"
            throw new Exception("Ключи ЭЦП не найдены!");
        }
        SHA1Digest dig = new SHA1Digest();
        RSADigestSigner signer = new RSADigestSigner(dig);
        CipherParameters cipa = (CipherParameters) PubKeyServ;
        signer.init(false, cipa);
        signer.update(mesg, 0, mesg.length);
        BigInteger bi_sign = new BigInteger(XDSF.ToReserveEndianByteArray(sig));
        return signer.verifySignature(BigIntegers.asUnsignedByteArray(bi_sign));
    }
Заключение.
С высоты прожитых лет с того момента, сейчас кажется, что это ерунда и пара пустяков. Но тогда на эту разработку ушло 19 дней! и приходилось для себя "разжевывать" каждый закоулок кода. А смысл последних двух предложений в том, что помогайте и поощаряйте, тех кто реально тянется к знаниям и совершествованию, а для кого очередной копипаст из интернета это всего лишь следующий "распил" - просто "по шапке, по шапке" их ))
Вставить карту яндекс на сайт

Работа с сетью из мидлета средствами JavaMe

Использование библиотеки Bouncy Castle в JavaMe (Часть 1)

Использование библиотеки Bouncy Castle в JavaMe (Часть 2)

Запись звонков

Найти человека по номеру телефона

Файловая система Android

Запись звука на Android

Локация в JavaCard

Философия прослушки

MBLOC.RU
Copyright © MBLOC.RU Все права защищены от взлома и подделки данного сайта. Распространение информации без разрешения владельца данного сайта запрещается и карается всей строгостью закона.