Mengubah Kunci Rahasia menjadi String dan Sebaliknya


102

Saya membuat kunci dan perlu menyimpannya di DB, jadi saya mengubahnya menjadi String, tetapi untuk mendapatkan kembali kunci dari String. Apa cara yang mungkin untuk mencapai ini?

Kode saya adalah,

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

Bagaimana saya bisa mendapatkan kembali kunci dari String?


1
Perhatikan bahwa konversi kunci menjadi string hanya dilakukan jika benar-benar diperlukan. Tidak ada metode eksplisit untuk menghancurkan Stringinstance di Java sementara objek kunci dan array byte mungkin dihapus. Artinya, kunci dapat tetap tersedia dalam memori untuk jangka waktu yang lebih lama. Menggunakan (dilindungi kata sandi) KeyStore, sebaiknya yang didukung oleh sistem runtime / OS atau bahkan perangkat keras harus lebih disukai.
Maarten Bodewes

Jawaban:


272

Anda dapat mengonversi SecretKeyke array byte ( byte[]), lalu Base64 menyandikannya ke a String. Untuk mengonversi kembali ke a SecretKey, Base64 mendekode String dan menggunakannya di a SecretKeySpecuntuk membangun kembali aslinya SecretKey.

Untuk Java 8

SecretKey ke String:

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

String ke 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"); 

Untuk Java 7 dan sebelumnya (termasuk Android):

CATATAN I: Anda dapat melewati bagian encoding / decoding Base64 dan hanya menyimpannya byte[]di SQLite. Meskipun demikian, melakukan encoding / decoding Base64 bukanlah operasi yang mahal dan Anda dapat menyimpan string di hampir semua DB tanpa masalah.

CATATAN II: Versi Java sebelumnya tidak menyertakan Base64 di salah satu paket java.langatau java.util. Namun, Anda dapat menggunakan codec dari Apache Commons Codec , Bouncy Castle, atau Guava .

SecretKey ke 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)}

String ke 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 Apa paket untuk kelas "Base64"
Tukar L

@SwapL Ini android.util.Base64. Lihat tautan ini: developer.android.com/reference/android/util/Base64.html
Jabari

@ MaartenBodewes-owlstead Kebanyakan orang belum menggunakan Java 8. Saya menggunakan ini di Android, yang pasti belum di 8 (dan mungkin tidak akan untuk beberapa waktu). Harap jangan mengedit jawaban seseorang dengan asumsi konteks.
Jabari

@ MaartenBodewes-owlstead Komentar Anda sepenuhnya mengabaikan kalimat pertama saya: "Kebanyakan orang belum menggunakan Java 8". Jawaban Anda akan memunculkan kesalahan pengecualian untuk sebagian besar pengguna Java, Android dan non-Android. Oleh karena itu, saran Anda untuk menambahkan cuplikan selain jawaban saat ini akan memberikan solusi yang lebih lengkap. FYI, saya tidak "sentimental" sehubungan dengan jawaban saya. Faktanya, saya menukar DES dengan AES karena ini adalah peningkatan keamanan yang pasti bijaksana (serta lebih sejalan dengan kode dalam pertanyaan asli).
Jabari

@ MaartenBodewes-owlstead Sekali lagi ... apa yang Anda tambahkan akan memunculkan kesalahan pengecualian "NoSuchAlgorithmException". Silakan Lihat: docs.oracle.com/javase/7/docs/api/javax/crypto/… Saya akan memperbaiki ...
Jabari

5

Untuk menunjukkan betapa menyenangkannya membuat beberapa fungsi yang gagal dengan cepat, saya telah menulis 3 fungsi berikut.

Satu membuat kunci AES, satu mengkodekannya dan satu lagi mendekodekannya kembali. Ketiga metode ini dapat digunakan dengan Java 8 (tanpa ketergantungan kelas internal atau ketergantungan luar):

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;
}

2
Perhatikan bahwa menyimpan / mengambil kunci mungkin tidak berfungsi jika penyimpanan kunci berada di modul keamanan perangkat keras (atau lokasi lain di mana getEncoded()tidak tersedia).
Maarten Bodewes

1

Sebenarnya apa yang Luis usulkan tidak berhasil untukku. Saya harus mencari cara lain. Inilah yang membantu saya. Mungkin bisa membantu Anda juga. Tautan:

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

  2. Informasi encoder: https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Encoder.html

  3. Informasi decoder: https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Decoder.html

Potongan kode: Untuk encoding:

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

Untuk decoding:

byte[] encodedKey = Base64.getDecoder().decode(temp);
SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "DES");

0

Anda tidak ingin menggunakan .toString().

Perhatikan bahwa SecretKey mewarisi dari java.security.Key, yang dengan sendirinya mewarisi dari Serializable. Jadi kuncinya di sini (tidak ada permainan kata-kata) adalah untuk membuat serial kunci menjadi ByteArrayOutputStream, dapatkan array byte [] dan menyimpannya ke dalam db. Proses kebalikannya adalah untuk mendapatkan array byte [] dari db, membuat ByteArrayInputStream dari array byte [], dan deserialisasi SecretKey dari itu ...

... atau bahkan lebih sederhana, cukup gunakan .getEncoded()metode yang diwarisi dari java.security.Key (yang merupakan antarmuka induk dari SecretKey). Metode ini mengembalikan array byte [] yang disandikan dari Key / SecretKey, yang dapat Anda simpan atau ambil dari database.

Ini semua dengan asumsi implementasi SecretKey Anda mendukung encoding. Jika tidak, getEncoded()akan mengembalikan null.

edit:

Anda harus melihat javadocs Key / SecretKey (tersedia tepat di awal halaman google):

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

Atau ini dari CodeRanch (juga ditemukan dengan pencarian google yang sama):

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


Serializable adalah pola anti IMO hari ini setiap kali Anda memiliki pendekatan alternatif. Jawaban yang disetujui yang menyandikan dan menerjemahkan base64 jauh lebih baik.
pengguna2223059

0

Mengubah SecretKeySpec menjadi String dan sebaliknya: Anda dapat menggunakan getEncoded()metode SecretKeySpecyang akan memberi byteArray, dari situ Anda dapat menggunakan encodeToString()untuk mendapatkan stringnilai SecretKeySpecdalamBase64 objek.

Saat mengonversi SecretKeySpecke String: gunakan decode()dalam Base64akan memberi byteArray, dari situ Anda dapat membuat contoh untuk SecretKeySpecdengan params sebagai byteArrayuntuk mereproduksi 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

coba ini, ini berfungsi tanpa Base64 (yang hanya disertakan di JDK 1.8), kode ini juga berjalan di versi java sebelumnya :)

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;

} 
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.