Przekształcanie tajnego klucza w łańcuch i odwrotnie

102

Generuję klucz i muszę go przechowywać w DB, więc konwertuję go na ciąg, ale aby odzyskać klucz z ciągu. Jakie są możliwe sposoby osiągnięcia tego?

Mój kod to

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

Jak mogę odzyskać klucz z String?

Princeyesuraj
źródło
1
Należy pamiętać, że konwersja kluczy na łańcuchy powinna być wykonywana tylko wtedy, gdy jest to absolutnie konieczne. W StringJavie nie ma jawnej metody niszczenia instancji, podczas gdy kluczowe obiekty i tablice bajtów mogą zostać wyczyszczone. Oznacza to, że klucze mogą pozostać dostępne w pamięci przez dłuższy czas. KeyStorePreferowane powinno być używanie (chronione hasłem) , najlepiej obsługiwane przez system / system operacyjny lub nawet sprzęt.
Maarten Bodewes

Odpowiedzi:

273

Możesz przekonwertować SecretKeytablicę bajtów ( byte[]), a następnie zakodować ją w formacie Base64 do pliku String. Aby przekonwertować z powrotem na a SecretKey, Base64 dekoduje ciąg i używa go w a, SecretKeySpecaby odbudować oryginał SecretKey.

W przypadku języka Java 8

SecretKey to String:

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

Ciąg do 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 i starsze (w tym Android):

UWAGA I: możesz pominąć część kodowania / dekodowania Base64 i po prostu zapisać byte[]w SQLite. To powiedziawszy, wykonywanie kodowania / dekodowania Base64 nie jest kosztowną operacją i można przechowywać ciągi znaków w prawie każdej bazie danych bez problemów.

UWAGA II: Wcześniejsze wersje Java nie zawierają Base64 w żadnym z pakietów java.langlub java.util. Możliwe jest jednak użycie kodeków z Apache Commons Codec , Bouncy Castle lub Guava .

SecretKey to String:

// 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)}

Ciąg do 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
źródło
@Jabari Jaki jest pakiet dla klasy "Base64"
Zamień L
@SwapL To android.util.Base64. Sprawdź ten link: developer.android.com/reference/android/util/Base64.html
Jabari
@ MaartenBodewes-owlstead Większość ludzi nie używa jeszcze Java 8. Użyłem tego na Androidzie, który zdecydowanie nie jest jeszcze na 8 (i prawdopodobnie nie będzie przez jakiś czas). Nie edytuj czyjejś odpowiedzi na podstawie kontekstu.
Jabari,
@ MaartenBodewes-owlstead Twój komentarz całkowicie ignoruje moje pierwsze zdanie: „Większość ludzi jeszcze nie używa Java 8”. Twoja odpowiedź spowoduje zgłoszenie błędów wyjątków dla większości użytkowników języka Java, zarówno z systemem Android, jak i bez niego. To powiedziawszy, Twoja sugestia dodania fragmentu kodu oprócz bieżącej odpowiedzi zapewniłaby pełniejsze rozwiązanie. FYI, nie jestem „sentymentalny” w odniesieniu do mojej odpowiedzi. W rzeczywistości zamieniłem DES na AES, ponieważ jest to zdecydowanie poprawa pod względem bezpieczeństwa (a także jest bardziej zgodna z kodem w pierwotnym pytaniu).
Jabari
@ MaartenBodewes-owlstead Jeszcze raz ... to, co dodałeś, spowoduje zgłoszenie błędów wyjątku „NoSuchAlgorithmException”. Zobacz: docs.oracle.com/javase/7/docs/api/javax/crypto/ ... Naprawię ...
Jabari
5

Aby pokazać, jak fajnie jest tworzyć niektóre funkcje, które szybko zawodzą , napisałem następujące 3 funkcje.

Jeden tworzy klucz AES, jeden go koduje, a drugi dekoduje z powrotem. Te trzy metody mogą być używane z Javą 8 (bez zależności od klas wewnętrznych lub zewnętrznych zależności):

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
źródło
2
Należy pamiętać, że przechowywanie / odzyskiwanie kluczy może nie działać, jeśli magazyn kluczy znajduje się na sprzętowym module zabezpieczeń (lub w innym miejscu, w którym getEncoded()nie jest dostępny).
Maarten Bodewes
1

Właściwie to, co zaproponował Luis, nie zadziałało dla mnie. Musiałem wymyślić inny sposób. To mi pomogło. Może ci też pomóc. Spinki do mankietów:

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

  2. Informacje o enkoderze: https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Encoder.html

  3. Informacje o dekoderze: https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Decoder.html

Fragmenty kodu: do kodowania:

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

Do dekodowania:

byte[] encodedKey = Base64.getDecoder().decode(temp);
SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "DES");
Revanth Kumar
źródło
0

Nie chcesz używać .toString().

Zauważ, że SecretKey dziedziczy po java.security.Key, która sama dziedziczy po Serializable. Zatem kluczem tutaj (gra słów nie jest zamierzona) jest serializacja klucza do ByteArrayOutputStream, pobranie tablicy bajtów [] i zapisanie jej w bazie danych. Odwrotnym procesem byłoby pobranie tablicy bajtów [] z bazy danych, utworzenie ByteArrayInputStream z tablicy bajtów [] i deserializacja klucza SecretKey z niej ...

... lub nawet prościej, po prostu użyj .getEncoded()metody odziedziczonej po java.security.Key (która jest interfejsem nadrzędnym SecretKey). Ta metoda zwraca zakodowaną tablicę byte [] poza Key / SecretKey, którą można przechowywać lub pobierać z bazy danych.

To wszystko przy założeniu, że Twoja implementacja SecretKey obsługuje kodowanie. W przeciwnym razie getEncoded()zwróci wartość null.

edytować:

Powinieneś spojrzeć na javadocs Key / SecretKey (dostępne na początku strony Google):

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

Lub to z CodeRanch (również znalezione w tej samej wyszukiwarce Google):

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

luis.espinal
źródło
Serializable to obecnie anty-wzorzec IMO, ilekroć miałeś alternatywne podejście. Zatwierdzona odpowiedź, która base64 koduje i dekoduje, jest znacznie lepsza.
user2223059
0

Konwersja SecretKeySpec String i odwrotnie: można użyć getEncoded()metody w SecretKeySpecktórych da byteArray, z których można użyć encodeToString(), aby uzyskać stringwartość od SecretKeySpecw Base64obiekcie.

Konwertując SecretKeySpecna String: użyj decode()w Base64da byteArray, z tego możesz utworzyć instancję dla SecretKeySpecz parametrami jako byteArraydo odtworzenia twojego 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");
anand krish
źródło
-1

spróbuj tego, działa bez Base64 (który jest zawarty tylko w JDK 1.8), ten kod działa również w poprzedniej wersji 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;

} 
daniel
źródło