Преобразование секретного ключа в строку и наоборот

102

Я генерирую ключ, и мне нужно сохранить его в БД, поэтому я конвертирую его в String, но чтобы получить обратно ключ из String. Каковы возможные пути достижения этого?

Мой код,

SecretKey key = KeyGenerator.getInstance("AES").generateKey();
String stringKey=key.toString();
System.out.println(stringKey);

Как я могу вернуть ключ из String?

Princeyesuraj
источник
1
Обратите внимание, что преобразование ключей в строку должно выполняться только в случае крайней необходимости. В StringJava нет явного метода уничтожения экземпляров, в то время как ключевые объекты и байтовые массивы могут быть очищены. Это означает, что ключи могут оставаться доступными в памяти в течение более длительного периода времени. KeyStoreПредпочтительно использовать (защищенный паролем) , предпочтительно поддерживаемый системой / ОС среды выполнения или даже оборудованием.
Maarten Bodewes

Ответы:

273

Вы можете преобразовать SecretKeyего в байтовый массив ( byte[]), а затем кодировать Base64 в String. Чтобы преобразовать обратно в a SecretKey, Base64 декодирует строку и использует ее в a SecretKeySpecдля восстановления исходного кода SecretKey.

Для Java 8

Секретный ключ к строке:

// create new key
SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();
// get base64 encoded version of the key
String encodedKey = Base64.getEncoder().encodeToString(secretKey.getEncoded());

Строка для SecretKey:

// decode the base64 encoded string
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
// rebuild key using SecretKeySpec
SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); 

Для Java 7 и более ранних версий (включая Android):

ПРИМЕЧАНИЕ I: вы можете пропустить часть кодирования / декодирования Base64 и просто сохранить byte[]в SQLite. Тем не менее, выполнение кодирования / декодирования Base64 не является дорогой операцией, и вы можете без проблем хранить строки практически в любой БД.

ПРИМЕЧАНИЕ II. Более ранние версии Java не включают Base64 в один из пакетов java.langили java.util. Однако можно использовать кодеки из Apache Commons Codec , Bouncy Castle или Guava .

Секретный ключ к строке:

// CREATE NEW KEY
// GET ENCODED VERSION OF KEY (THIS CAN BE STORED IN A DB)

    SecretKey secretKey;
    String stringKey;

    try {secretKey = KeyGenerator.getInstance("AES").generateKey();}
    catch (NoSuchAlgorithmException e) {/* LOG YOUR EXCEPTION */}

    if (secretKey != null) {stringKey = Base64.encodeToString(secretKey.getEncoded(), Base64.DEFAULT)}

Строка для SecretKey:

// DECODE YOUR BASE64 STRING
// REBUILD KEY USING SecretKeySpec

    byte[] encodedKey     = Base64.decode(stringKey, Base64.DEFAULT);
    SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
Джабари
источник
@Jabari Какой пакет для класса "Base64"
Swap L
@SwapL Это android.util.Base64. Посмотрите эту ссылку: developer.android.com/reference/android/util/Base64.html
Jabari
@ MaartenBodewes-owlstead Большинство людей еще не используют Java 8. Я использовал это в Android, которого определенно нет на 8 (и, вероятно, не будет в ближайшее время). Пожалуйста, не редактируйте чей-то ответ исходя из контекста.
Джабари
@ MaartenBodewes-owlstead Ваш комментарий полностью игнорирует мое первое предложение: «Большинство людей еще не используют Java 8». Ваш ответ вызовет ошибки исключения для подавляющего большинства пользователей Java, Android и не Android. Тем не менее, ваше предложение добавить фрагмент в дополнение к текущему ответу предоставит более полное решение. К вашему сведению, я не «сентиментален» в отношении своего ответа. На самом деле я заменил DES на AES, потому что это определенное улучшение безопасности (а также больше соответствует коду в исходном вопросе).
Джабари
@ MaartenBodewes-owlstead Опять же ... то, что вы добавили, вызовет ошибку исключения "NoSuchAlgorithmException". См .: docs.oracle.com/javase/7/docs/api/javax/crypto/ ... Я исправлю ...
Джабари
5

Чтобы показать, как весело создавать некоторые функции, которые быстро выходят из строя, я написал следующие 3 функции.

Один создает ключ AES, один кодирует его, а другой декодирует обратно. Эти три метода можно использовать с Java 8 (вне зависимости от внутренних классов или внешних зависимостей):

public static SecretKey generateAESKey(int keysize)
        throws InvalidParameterException {
    try {
        if (Cipher.getMaxAllowedKeyLength("AES") < keysize) {
            // this may be an issue if unlimited crypto is not installed
            throw new InvalidParameterException("Key size of " + keysize
                    + " not supported in this runtime");
        }

        final KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(keysize);
        return keyGen.generateKey();
    } catch (final NoSuchAlgorithmException e) {
        // AES functionality is a requirement for any Java SE runtime
        throw new IllegalStateException(
                "AES should always be present in a Java SE runtime", e);
    }
}

public static SecretKey decodeBase64ToAESKey(final String encodedKey)
        throws IllegalArgumentException {
    try {
        // throws IllegalArgumentException - if src is not in valid Base64
        // scheme
        final byte[] keyData = Base64.getDecoder().decode(encodedKey);
        final int keysize = keyData.length * Byte.SIZE;

        // this should be checked by a SecretKeyFactory, but that doesn't exist for AES
        switch (keysize) {
        case 128:
        case 192:
        case 256:
            break;
        default:
            throw new IllegalArgumentException("Invalid key size for AES: " + keysize);
        }

        if (Cipher.getMaxAllowedKeyLength("AES") < keysize) {
            // this may be an issue if unlimited crypto is not installed
            throw new IllegalArgumentException("Key size of " + keysize
                    + " not supported in this runtime");
        }

        // throws IllegalArgumentException - if key is empty
        final SecretKeySpec aesKey = new SecretKeySpec(keyData, "AES");
        return aesKey;
    } catch (final NoSuchAlgorithmException e) {
        // AES functionality is a requirement for any Java SE runtime
        throw new IllegalStateException(
                "AES should always be present in a Java SE runtime", e);
    }
}

public static String encodeAESKeyToBase64(final SecretKey aesKey)
        throws IllegalArgumentException {
    if (!aesKey.getAlgorithm().equalsIgnoreCase("AES")) {
        throw new IllegalArgumentException("Not an AES key");
    }

    final byte[] keyData = aesKey.getEncoded();
    final String encodedKey = Base64.getEncoder().encodeToString(keyData);
    return encodedKey;
}
Maarten Bodewes
источник
2
Обратите внимание, что хранение / получение ключей может не работать, если хранилище ключей находится на аппаратном модуле безопасности (или в любом другом месте, где getEncoded()оно недоступно).
Maarten Bodewes
1

На самом деле то, что предложил Луис, меня не устроило. Пришлось придумать другой способ. Вот что мне помогло. Тебе тоже может помочь. Ссылки:

  1. * .getEncoded (): https://docs.oracle.com/javase/7/docs/api/java/security/Key.html

  2. Информация о кодировщике: https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Encoder.html

  3. Информация о декодере: https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Decoder.html

Фрагменты кода: Для кодирования:

String temp = new String(Base64.getEncoder().encode(key.getEncoded()));

Для расшифровки:

byte[] encodedKey = Base64.getDecoder().decode(temp);
SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "DES");
Ревант Кумар
источник
0

Вы не хотите использовать .toString().

Обратите внимание, что SecretKey наследуется от java.security.Key, который сам наследуется от Serializable. Таким образом, ключевым моментом здесь (без каламбура) является сериализация ключа в ByteArrayOutputStream, получение массива byte [] и сохранение его в db. Обратный процесс заключался бы в том, чтобы получить массив byte [] из базы данных, создать ByteArrayInputStream из массива byte [] и десериализовать SecretKey с него ...

... или, что еще проще, просто используйте .getEncoded()метод, унаследованный от java.security.Key (который является родительским интерфейсом SecretKey). Этот метод возвращает закодированный массив byte [] из Key / SecretKey, который вы можете сохранить или получить из базы данных.

Все это при условии, что ваша реализация SecretKey поддерживает кодирование. В противном случае getEncoded()вернет null.

редактировать:

Вам следует взглянуть на документацию Key / SecretKey javadocs (доступную прямо в начале страницы Google):

http://download.oracle.com/javase/6/docs/api/java/security/Key.html

Или это из CodeRanch (также найдено с помощью того же поиска Google):

http://www.coderanch.com/t/429127/java/java/Convertion-between-SecretKey-String-or

luis.espinal
источник
Сериализуемый - это антипаттерн в наши дни, ИМО, всякий раз, когда у вас был альтернативный подход. Утвержденный ответ, который кодирует и декодирует base64, намного лучше.
user2223059
0

Преобразование SecretKeySpec в String и наоборот: вы можете использовать getEncoded()метод, в SecretKeySpecкотором будет давать byteArray, из которого вы можете encodeToString()получить stringзначение SecretKeySpecвBase64 объекте.

При преобразовании SecretKeySpecв String: use decode()in Base64предоставит byteArray, из этого вы можете создать экземпляр для SecretKeySpecс параметрами как byteArrayдля воспроизведения вашего SecretKeySpec.

String mAesKey_string;
SecretKeySpec mAesKey= new SecretKeySpec(secretKey.getEncoded(), "AES");

//SecretKeySpec to String 
    byte[] byteaes=mAesKey.getEncoded();
    mAesKey_string=Base64.encodeToString(byteaes,Base64.NO_WRAP);

//String to SecretKeySpec
    byte[] aesByte = Base64.decode(mAesKey_string, Base64.NO_WRAP);
    mAesKey= new SecretKeySpec(aesByte, "AES");
Ананд Криш
источник
-1

попробуйте, он работает без Base64 (который включен только в JDK 1.8), этот код работает и в предыдущей версии java :)

private static String SK = "Secret Key in HEX";


//  To Encrupt

public static String encrypt( String Message ) throws Exception{

    byte[] KeyByte = hexStringToByteArray( SK);
    SecretKey k = new SecretKeySpec(KeyByte, 0, KeyByte.length, "DES");

    Cipher c = Cipher.getInstance("DES","SunJCE");
    c.init(1, k);
    byte mes_encrypted[] = cipher.doFinal(Message.getBytes());

    String MessageEncrypted = byteArrayToHexString(mes_encrypted);
    return MessageEncrypted;
}

//  To Decrypt

public static String decrypt( String MessageEncrypted )throws Exception{

    byte[] KeyByte = hexStringToByteArray( SK );
    SecretKey k = new SecretKeySpec(KeyByte, 0, KeyByte.length, "DES");

    Cipher dcr =  Cipher.getInstance("DES","SunJCE");
    dc.init(Cipher.DECRYPT_MODE, k);
    byte[] MesByte  = hexStringToByteArray( MessageEncrypted );
    byte mes_decrypted[] = dcipher.doFinal( MesByte );
    String MessageDecrypeted = new String(mes_decrypted);

    return MessageDecrypeted;
}

public static String byteArrayToHexString(byte bytes[]){

    StringBuffer hexDump = new StringBuffer();
    for(int i = 0; i < bytes.length; i++){
    if(bytes[i] < 0)
    {   
        hexDump.append(getDoubleHexValue(Integer.toHexString(256 - Math.abs(bytes[i]))).toUpperCase());
    }else
    {
        hexDump.append(getDoubleHexValue(Integer.toHexString(bytes[i])).toUpperCase());
    }
    return hexDump.toString();

}



public static byte[] hexStringToByteArray(String s) {

    int len = s.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2)
    {   
        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16));
    }
    return data;

} 
Дэниел
источник