company_banner

Шифрование и генерация случайных чисел в Android приложениях. Тестовые примеры

    В этой статье мы приведем тестовые фрагменты кода, реализующего две основополагающие с точки зрения безопасности функции в Android приложениях: генерацию случайных чисел и шифрование данных. Рекомендуем попробовать все приведенные варианты, а по прочтении текста – скомпилировать тестовое приложение, скачав его по ссылке.

    Шифрование данных

    Шифрование имеет важное значение, поскольку позволяет скрыть от посторонних глаз то, что им не следует видеть. Мобильные устройства хранят все больше и больше значимой информации, и защитить ее – прямая обязанность каждого разработчика.
    Существует два варианта шифрования данных под Android: с использованием Java Crypto API и OpenSSL API (нативный код). Мы рассмотрим оба.



    Java Crypto API
    Использовать Java Crypto API под Android очень просто. Сначала вам необходимо сгенерировать ключ шифрования. За это отвечает класс KeyGenerator в пэкедже javax.crypto.

    mKey = null;
    try {
    	kgen = KeyGenerator.getInstance("AES");
    	mKey = kgen.generateKey();
    
    } catch (NoSuchAlgorithmException e) {
    	e.printStackTrace();
    }
    

    Теперь вы можете использовать сгенерированный ключ для шифрования файлов с данными. Для этого зашифруем блоки байтов по алгоритму AES с помощью javax.crypto.

    // open stream to read origFilepath. We are going to save encrypted contents to outfile
    	InputStream fis = new FileInputStream(origFilepath);		
    	File outfile = new File(encFilepath);
    	int read = 0;
    	if (!outfile.exists())
    		outfile.createNewFile();
    		
    	FileOutputStream encfos = new FileOutputStream(outfile);
    	// Create Cipher using "AES" provider
    	Cipher encipher = Cipher.getInstance("AES");
    	encipher.init(Cipher.ENCRYPT_MODE, mKey);
    	CipherOutputStream cos = new CipherOutputStream(encfos, encipher);
    		
    	// capture time it takes to encrypt file
    	start = System.nanoTime();
    	Log.d(TAG, String.valueOf(start));
    		
    	byte[] block = new byte[mBlocksize];
    		
    	while ((read = fis.read(block,0,mBlocksize)) != -1) {
    		cos.write(block,0, read);
    	}
    	cos.close();
    	stop = System.nanoTime();
    		
    	Log.d(TAG, String.valueOf(stop));
    	seconds = (stop - start) / 1000000;// for milliseconds
    	Log.d(TAG, String.valueOf(seconds));
    		
    	fis.close();
    


    OpenSSL API
    Шифрование данных через OpenSSL под Android требует написания нативного кода С, который доступен в Java через вызовы JNI. Это отнимает больше времени, зато быстродействие в результате будет выше.
    Для начала сгенерируем ключ и iv.

    unsigned char cKeyBuffer[KEYSIZE/sizeof(unsigned char)];
    unsigned char iv[] = "01234567890123456";
    int opensslIsSeeded = 0;
    if (!opensslIsSeeded) {
    	if (!RAND_load_file("/dev/urandom", seedbytes)) {
    		return -1;
    	}
    	opensslIsSeeded = 1;
    }
    
    if (!RAND_bytes((unsigned char *)cKeyBuffer, KEYSIZE )) {
    }
    


    Теперь мы можем использовать сгенерированный ключ (cKeyBuffer) для шифрования файла. Инициализируем EVP с помощью вашего ключа и iv. Теперь подаем блоки байтов на вход функции EVP_EncryptUpdate. Последняя порция байтов из вашего файла должна быть скормлена функции EVP_EncryptFinal_ex.

    if (!(EVP_EncryptInit_ex(e_ctx, EVP_aes_256_cbc(), NULL, cKeyBuffer, iv ))) {
    	ret = -1;
    	printf( "ERROR: EVP_ENCRYPTINIT_EXn");
    }
    	
    // go through file, and encrypt
    if ( orig_file != NULL ) {
       	origData = new unsigned char[aes_blocksize];
        	encData = new unsigned char[aes_blocksize+EVP_CIPHER_CTX_block_size(e_ctx)]; // potential for encryption to be 16 bytes longer than original
    
    	printf( "Encoding file: %sn", filename);
    
    	bytesread = fread(origData, 1, aes_blocksize, orig_file);
    	// read bytes from file, then send to cipher
    	while ( bytesread ) {
    
    
    		if (!(EVP_EncryptUpdate(e_ctx, encData, &len, origData, bytesread))) {
    			ret = -1;
    			printf( "ERROR: EVP_ENCRYPTUPDATEn");
    		}
    		encData_len = len;
    
    		fwrite(encData, 1, encData_len, enc_file );
    		// read more bytes
    		bytesread = fread(origData, 1, aes_blocksize, orig_file);
    	}
    	// last step encryption
    	if (!(EVP_EncryptFinal_ex(e_ctx, encData, &len))) {
    		ret = -1;
    		printf( "ERROR: EVP_ENCRYPTFINAL_EXn");
    	}
    	encData_len = len;
    
    	fwrite(encData, 1, encData_len, enc_file );
    
    	// free cipher
    	EVP_CIPHER_CTX_free(e_ctx);
    

    Оригинал статьи на сайте Intel IDZ
    Исходники тестового приложения

    Генерация случайных чисел

    Генератор случайных чисел (RNG) – это программа или устройство для производства случайной последовательности чисел на определенном промежутке. RNG является жизненно важным для безопасности приложения. В реальности криптографический протокол может быть очень надежным, но при этом подверженным разнообразным атакам из-за того, что в своей основе использует слабые методы генерации ключа. Для усиления ключа и повышения надежности всей системы в целом может использоваться аппаратная поддержка RNG.



    Существует целых 4 способа сгенерировать случайные числа в Android:
    • java.util.random
    • java.security.SecureRandom
    • /dev/urandom
    • OpenSSL API

    Однако, если вы используете RNG для генерации ключа, защищающего ваши данные, использовать обычный класс Random не рекомендуется, так его легче всего взломать. Остальные 3 метода обеспечивают более надежную защиту.

    java.util.random
    Использовать Java Random Number API очень просто. Вызов Random.nextInt() возвратит 4-байтное случайное значение (общее количество возможных значений – 232). Это API вполне годится для случаев, когда не требуется полагаться на действительно случайные числа.

    for (int i = 0; i < lastVal; i += 2) {
    	dataRandomPoints[i] = (rand.nextInt() % widget_width);
    	dataRandomPoints[i+1] = (rand.nextInt() % widget_height);
    
    }
    


    java.security.SecureRandom
    SecureRandom похож на java.util.Random в том смысле, что также возвращает 4-байтовое значение. SecureRandom криптографически более надежен, однако разработчики должны ознакомиться с недавней рекомендацией генерировать затравочную величину с помощью /dev/urandom для SecureRandom перед генерацией случайных чисел. В примере ниже /dev/urandom не используется.

    SecureRandom srand = new SecureRandom();
    shouldDraw = (srand.nextInt() % randomMod );
    

    /dev/urandom
    Во всех операционных системах семейства Linux, включая Android, имеется специальный файл, созданный ядром, с помощью которого можно предоставить случайные числа приложениям. Среди всех 4 способов этот самый медленный, он генерирует криптографически безопасные значения с высокой энтропией путем объединения шумовых величин из различных частей операционной системы (например, драйверов устройств) для RNG. Мы можем получить случайное число непосредственно из ядра, прочитав файл /dev/urandom. /dev/urandom имеет доступ к аппаратному RNG, если таковой имеется.

    unsigned int cKeyBuffer[keysize];
    memset(cKeyBuffer, 0, sizeof(unsigned int) * keysize);
    
    FILE *fin;
    strcpy(filein, "/dev/urandom");
    fin = fopen(filein, "rb");
    
    if (fin != NULL) {
    	fread(cKeyBuffer, sizeof(int), keysize, fin);
    	fclose (fin);
    }
    

    OpenSSL API
    Мы также можем использовать OpenSSL API для получения случайных чисел в нативном коде С. В OpenSSL возможно использование затравочных байт из /dev/urandom для генерации криптографически безопасных случайных чисел. OpenSSL API обратится к аппаратному RNG, если таковой имеется.

    int seedbytes = 1024;
    unsigned int cKeyBuffer[keysize];
    memset(cKeyBuffer, 0, sizeof(unsigned int) * keysize);
    
    if (!opensslIsSeeded) {
    
    	if (!RAND_load_file("/dev/urandom", seedbytes)) {
    		__android_log_print(ANDROID_LOG_ERROR, TAG, "Failed to seed OpenSSL RNG");
    		return jKeyBuffer;
    	}
    
    	opensslIsSeeded = 1;
    }
    
    if (!RAND_bytes((unsigned char *)cKeyBuffer, keysize * sizeof(int))) {
    	__android_log_print(ANDROID_LOG_ERROR, TAG, "Faled to create OpenSSSL random integers: %ul", ERR_get_error);
    }
    

    Оригинал статьи на сайте Intel IDZ
    Исходники тестового приложения
    • +11
    • 15.4k
    • 3
    Intel
    165.11
    Company
    Share post

    Comments 3

      0
      Кстати, rand.nextInt() и srand.nextInt() имеют соответствующие функции задания seed'а? Типа srand() и srandom()? Не игнорируют ли они их в процессе работы?
        0
        Seed передается в конструктор классам Random и SecureRandom и, конечно, не игнорируется. :)
          0
          Ну, некоторые «версии» PHP успешно игнорируют переинициализацию ГСЧ… Сложно сказать что хуже.

      Only users with full accounts can post comments. Log in, please.