비밀 키를 문자열로 또는 그 반대로 변환


102

키를 생성 중이며 DB에 저장해야하므로 문자열로 변환하지만 문자열에서 키를 다시 가져옵니다. 이를 수행하는 가능한 방법은 무엇입니까?

내 코드는

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

문자열에서 키를 어떻게 되 찾을 수 있습니까?


1
키를 문자열로 변환하는 것은 절대적으로 필요한 경우에만 수행해야합니다. String키 객체와 바이트 배열을 지울 수있는 동안 Java에서 인스턴스 를 삭제하는 명시적인 방법은 없습니다 . 이는 키가 더 오랜 기간 동안 메모리 내에서 사용 가능한 상태를 유지할 수 있음을 의미합니다. (비밀번호 보호) 사용 KeyStore, 가급적 런타임 시스템 / OS 또는 하드웨어에 의해 지원되는 것이 선호되어야합니다.
Maarten Bodewes

답변:


272

SecretKey바이트 배열 ( byte[]) 로 변환 한 다음 Base64로 String. 다시 SecretKey, Base64 로 변환하려면 문자열을 디코딩하고 a SecretKeySpec에서 사용하여 원본을 다시 빌드합니다 SecretKey.

Java 8의 경우

문자열에 대한 SecretKey :

// 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 인코딩 / 디코딩을 수행하는 것은 비용이 많이 드는 작업이 아니며 거의 모든 DB에 문제없이 문자열을 저장할 수 있습니다.

참고 II : 이전 Java 버전은 java.lang또는 java.util패키지 중 하나에 Base64를 포함하지 않습니다 . 그러나 Apache Commons Codec , Bouncy Castle 또는 Guava의 코덱을 사용할 수 있습니다 .

문자열에 대한 SecretKey :

// 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
자바리

@ MaartenBodewes-owlstead 대부분의 사람들은 아직 Java 8을 사용하지 않습니다. 나는 이것을 안드로이드에서 사용했는데, 그것은 확실히 아직 8에 있지 않다 (그리고 아마 당분간은 아닐 것이다). 맥락을 가정하여 다른 사람의 답변을 편집하지 마십시오.
Jabari

@ MaartenBodewes-owlstead 귀하의 의견은 내 첫 번째 문장을 완전히 무시합니다. "대부분의 사람들은 아직 Java 8을 사용하지 않습니다." 귀하의 답변은 대다수의 Java 사용자, Android 및 비 Android 모두에게 예외 오류를 발생시킵니다. 즉, 현재 답변에 스 니펫을 추가하는 것이 더 완벽한 솔루션을 제공 할 것입니다. 참고로, 나는 내 대답과 관련하여 "감정적"이 아닙니다. 사실, 보안 측면에서 확실히 개선 되었기 때문에 DES를 AES로 바꿨습니다 (원래 질문의 코드와 더 일치 함).
Jabari

@ MaartenBodewes-owlstead 다시 ... 추가 한 내용은 "NoSuchAlgorithmException"예외 오류를 발생시킵니다. 참조하십시오 : docs.oracle.com/javase/7/docs/api/javax/crypto/… 수정하겠습니다 ...
Jabari

5

빠르게 실패 하는 일부 함수를 만드는 것이 얼마나 재미 있는지 보여주기 위해 다음 세 가지 함수를 작성했습니다.

하나는 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;
}

2
키 저장소가 하드웨어 보안 모듈 (또는 getEncoded()사용할 수없는 다른 위치)에있는 경우 키 저장 / 검색이 작동 하지 않을 수 있습니다.
Maarten Bodewes

1

실제로 Luis가 제안한 것은 저에게 효과가 없었습니다. 나는 다른 방법을 찾아야했다. 이것이 나를 도왔습니다. 당신도 도울 수 있습니다. 연결:

  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는 Serializable에서 상속되는 java.security.Key에서 상속됩니다. 따라서 여기서 핵심은 (말장난이 아님) 키를 ByteArrayOutputStream으로 직렬화하고 byte [] 배열을 가져 와서 db에 저장하는 것입니다. 반대 프로세스는 db에서 byte [] 배열을 가져오고, byte [] 배열의 ByteArrayInputStream을 만들고, SecretKey를 deserialize하는 것입니다.

... 또는 더 간단하게 .getEncoded()java.security.Key (SecretKey의 부모 인터페이스)에서 상속 된 메서드를 사용하십시오 . 이 메서드는 데이터베이스에서 저장하거나 검색 할 수있는 Key / SecretKey에서 인코딩 된 byte [] 배열을 반환합니다.

이것은 모두 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


Serializable은 대체 접근 방식이있을 때마다 요즘 IMO에서 안티 패턴입니다. base64가 인코딩하고 디코딩하는 승인 된 답변이 훨씬 낫습니다.
user2223059

0

SecretKeySpec을 String으로 또는 그 반대로 변환 :getEncoded() in 값 을 가져 오는 데 사용할 수있는, SecretKeySpec제공 하는 메서드를 byteArray사용할 수 있습니다 .encodeToString()stringSecretKeySpecBase64 객체 .

변환하는 동안 SecretKeySpecString: 사용 decode()에하는 Base64줄 것이다 byteArray, 그에서 당신을 위해 인스턴스를 만들 수 있습니다 SecretKeySpec(가)로 PARAMS와 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;

} 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.