Как зашифровать строку с помощью криптографии с открытым ключом
Я пытаюсь реализовать свой собственный механизм шифрования RSA. Учитывая эти значения алгоритма RSA:
p = 61. // A prime number.
q = 53. // Also a prime number.
n = 3233. // p * q.
totient = 3120. // (p - 1) * (q - 1)
e = 991. // Co-prime to the totient (co-prime to 3120).
d = 1231. // d * e = 1219921, which is equal to the relation where 1 + k * totient = 1219921 when k = 391.
Я пытаюсь написать метод для шифрования каждого байта в строке и вернуть обратно зашифрованную строку:
public string Encrypt(string m, Encoding encoding)
{
byte[] bytes = encoding.GetBytes(m);
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)BigInteger.ModPow(bytes[i], e, n);
}
string encryptedString = encoding.GetString(bytes);
Console.WriteLine("Encrypted {0} as {1}.", m, encryptedString);
return encryptedString;
}
Очевидная проблема здесь в том, что BigInteger.ModPow(bytes[i], e, n)
может быть слишком большим, чтобы поместиться в байтовое пространство; это может привести к значениям размером более 8 бит. Как обойти эту проблему, все еще будучи в состоянии расшифровать зашифрованную строку байтов обратно в обычную строку?
Обновление: даже при шифровании от байта [] к байту [] вы можете столкнуться с ситуацией, когда шифрование этого байта с использованием алгоритма RSA выходит за пределы размера байта:
public byte[] Encrypt(string m, Encoding encoding)
{
byte[] bytes = encoding.GetBytes(m);
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)BigInteger.ModPow(bytes[i], e, n);
}
return bytes;
}
Обновление: моя проблема в том, что шифрование вызовет большее количество байтов, чем исходная строка ввода:
public byte[] Encrypt(string m, Encoding encoding)
{
byte[] bytes = encoding.GetBytes(m);
byte[] returnBytes = new byte[0];
for (int i = 0; i < bytes.Length; i++)
{
byte[] result = BigInteger.ModPow(bytes[i], (BigInteger)e, n).ToByteArray();
int preSize = returnBytes.Length;
Array.Resize(ref returnBytes, returnBytes.Length + result.Length);
result.CopyTo(returnBytes, preSize);
}
return returnBytes;
}
public string Decrypt(byte[] c, Encoding encoding)
{
byte[] returnBytes = new byte[0];
for (int i = 0; i < c.Length; i++)
{
byte[] result = BigInteger.ModPow(c[i], d, n).ToByteArray();
int preSize = returnBytes.Length;
Array.Resize(ref returnBytes, returnBytes.Length + result.Length);
result.CopyTo(returnBytes, preSize);
}
string decryptedString = encoding.GetString(returnBytes);
return decryptedString;
}
Если вы запустили этот код так:
byte[] encryptedBytes = engine.Encrypt("Hello, world.", Encoding.UTF8);
Console.WriteLine(engine.Decrypt(encryptedBytes, Encoding.UTF8));
Выход будет такой:
?♥D
?♥→☻►♦→☻►♦oD♦8? ?♠oj?♠→☻►♦;♂?♠♂♠?♠
Очевидно, что выходные данные не являются исходной строкой, потому что я не могу просто попытаться расшифровать каждый байт за раз, так как иногда два или более байтов зашифрованного текста представляют значение одного целого числа, которое мне нужно расшифровать обратно до одного байта оригинальная строка... так что я хочу знать, каков стандартный механизм для обработки этого.
3 ответа
Примечание: я обновил этот ответ. Пожалуйста, прокрутите вниз до обновления, чтобы узнать, как оно должно быть реализовано, потому что этот первый способ не является правильным способом шифрования RSA.
Один из способов, которым я могу подумать, это сделать так (но может не соответствовать стандартам), а также заметить, что это не дополняет:
public byte[] Encrypt(string m, Encoding encoding)
{
byte[] bytes = encoding.GetBytes(m);
byte[] returnBytes = new byte[0];
for (int i = 0; i < bytes.Length; i++)
{
byte[] result = BigInteger.ModPow(bytes[i], (BigInteger)e, n).ToByteArray();
int preSize = returnBytes.Length;
Array.Resize(ref returnBytes, returnBytes.Length + result.Length + 1);
(new byte[] { (byte)(result.Length) }).CopyTo(returnBytes, preSize);
result.CopyTo(returnBytes, preSize + 1);
}
return returnBytes;
}
public string Decrypt(byte[] c, Encoding encoding)
{
byte[] returnBytes = new byte[0];
for (int i = 0; i < c.Length; i++)
{
int dataLength = (int)c[i];
byte[] result = new byte[dataLength];
for (int j = 0; j < dataLength; j++)
{
i++;
result[j] = c[i];
}
BigInteger integer = new BigInteger(result);
byte[] integerResult = BigInteger.ModPow(integer, d, n).ToByteArray();
int preSize = returnBytes.Length;
Array.Resize(ref returnBytes, returnBytes.Length + integerResult.Length);
integerResult.CopyTo(returnBytes, preSize);
}
string decryptedString = encoding.GetString(returnBytes);
return decryptedString;
}
Это может стать кроссплатформенным, поскольку у вас есть возможность использовать другой тип данных для представления e или n и передачи его в бэкэнд-сервис C#, подобный этому. Вот тест:
string stringToEncrypt = "Mary had a little lamb.";
Console.WriteLine("Encrypting the string: {0}", stringToEncrypt);
byte[] encryptedBytes = engine.Encrypt(stringToEncrypt, Encoding.UTF8);
Console.WriteLine("Encrypted text: {0}", Encoding.UTF8.GetString(encryptedBytes));
Console.WriteLine("Decrypted text: {0}", engine.Decrypt(encryptedBytes, Encoding.UTF8));
Выход:
Encrypting the string: Mary had a little lamb.
Encrypted text: ☻6☻1♦☻j☻☻&♀☻g♦☻t☻☻1♦☻? ☻g♦☻1♦☻g♦☻?♥☻?☻☻7☺☻7☺☻?♥☻?♂☻g♦☻?♥☻1♦☻$☺☻
c ☻?☻
Decrypted text: Mary had a little lamb.
Обновление: все, что я сказал ранее, совершенно неправильно в реализации RSA. Неправильно, неправильно, неправильно! Это правильный способ сделать шифрование RSA:
- Преобразуйте вашу строку в тип данных BigInteger.
- Убедитесь, что ваше целое число меньше значения n, которое вы вычислили для своего алгоритма, иначе вы не сможете его расшифровать.
- Зашифруйте целое число. RSA работает только с целочисленным шифрованием. Это понятно
- Расшифруйте его из зашифрованного целого числа.
- Я не могу не удивляться, что класс BigInteger был в основном создан для криптографии.
В качестве примера:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace BytePadder
{
class Program
{
const int p = 61;
const int q = 53;
const int n = 3233;
const int totient = 3120;
const int e = 991;
const int d = 1231;
static void Main(string[] args)
{
// ---------------------- RSA Example I ----------------------
// Shows how an integer gets encrypted and decrypted.
BigInteger integer = 1000;
BigInteger encryptedInteger = Encrypt(integer);
Console.WriteLine("Encrypted Integer: {0}", encryptedInteger);
BigInteger decryptedInteger = Decrypt(encryptedInteger);
Console.WriteLine("Decrypted Integer: {0}", decryptedInteger);
// --------------------- RSA Example II ----------------------
// Shows how a string gets encrypted and decrypted.
string unencryptedString = "A";
BigInteger integer2 = new BigInteger(Encoding.UTF8.GetBytes(unencryptedString));
Console.WriteLine("String as Integer: {0}", integer2);
BigInteger encryptedInteger2 = Encrypt(integer2);
Console.WriteLine("String as Encrypted Integer: {0}", encryptedInteger2);
BigInteger decryptedInteger2 = Decrypt(encryptedInteger2);
Console.WriteLine("String as Decrypted Integer: {0}", decryptedInteger2);
string decryptedIntegerAsString = Encoding.UTF8.GetString(decryptedInteger2.ToByteArray());
Console.WriteLine("Decrypted Integer as String: {0}", decryptedIntegerAsString);
Console.ReadLine();
}
static BigInteger Encrypt(BigInteger integer)
{
if (integer < n)
{
return BigInteger.ModPow(integer, e, n);
}
throw new Exception("The integer must be less than the value of n in order to be decypherable!");
}
static BigInteger Decrypt(BigInteger integer)
{
return BigInteger.ModPow(integer, d, n);
}
}
}
Пример вывода:
Encrypted Integer: 1989
Decrypted Integer: 1000
String as Integer: 65
String as Encrypted Integer: 1834
String as Decrypted Integer: 65
Decrypted Integer as String: A
Ваш основной код для шифрования и дешифрования каждого байта - вызов ModPow
- работает, но вы собираетесь "неправильно разделить сообщение и зашифровать каждый фрагмент".
Чтобы показать, что ModPow
часть - то есть математика - хорошо, вот код, основанный на вашем, который шифрует string
к BigInteger[]
и назад:
using System;
using System.Linq;
using System.Numerics;
using System.Text;
class Test
{
const int p = 61;
const int q = 53;
const int n = 3233;
const int totient = 3120;
const int e = 991;
const int d = 1231;
static void Main()
{
var encrypted = Encrypt("Hello, world.", Encoding.UTF8);
var decrypted = Decrypt(encrypted, Encoding.UTF8);
Console.WriteLine(decrypted);
}
static BigInteger[] Encrypt(string text, Encoding encoding)
{
byte[] bytes = encoding.GetBytes(text);
return bytes.Select(b => BigInteger.ModPow(b, (BigInteger)e, n))
.ToArray();
}
static string Decrypt(BigInteger[] encrypted, Encoding encoding)
{
byte[] bytes = encrypted.Select(bi => (byte) BigInteger.ModPow(bi, d, n))
.ToArray();
return encoding.GetString(bytes);
}
}
Далее вам нужно прочитать больше о том, как byte[]
зашифрован в другой byte[]
использование RSA, включая все различные схемы заполнения и т. д. Это намного больше, чем просто вызов ModPow
на каждый байт.
Но, повторюсь, вы не должны делать это, чтобы закончить реализацию RSA. Шансы сделать это без каких-либо недостатков в безопасности очень малы. Это хорошо сделать для академического интереса, узнать больше о принципах криптографии, но оставить реальную реализацию экспертам. (Я далеко не эксперт в этой области - я никак не мог бы начать реализовывать свое собственное шифрование...)
Если вы хотите использовать шифрование RSA в C#, то вам не следует пытаться создать свой собственный. Для начала, простые числа, которые вы выбрали, вероятно, малы. P и Q должны быть большими простыми числами.
Вы должны проверить некоторые другие вопросы / ответы:
как использовать RSA для шифрования файлов (огромных данных) в C#
RSA Шифрование больших данных в C#
И другие ссылки: http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.encrypt(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.aspx