views:

231

answers:

4

In the similar question "Conversion of byte[] into a String and then back to a byte[]" is said to not to do the byte[] to String and back conversion, what looks like apply to most cases, mainly when you don't know the encoding used.

But, in my case I'm trying to save to a DB the javax.crypto.SecretKey data, and recoverd it after.

The interface provide a method getEncoded() which returns the key data encoded as byte[], and with another class I can use this byte[] to recover the key.

So, the question is, how do I write the key bytes as String, and later get back the byte[] to regenerate the key?

A: 

Couldn't you use the String constructor: String(byte[] data,String charsetName) as in:

byte[] data=new byte[1024];
String key=new String(data,"UTF-8");

and later you can do:

String key="mymostsecretaeskey";
byte[] data=key.getBytes("UTF-8");
smeg4brains
Very bad idea - the data will be arbitrary binary data, and that won't always be a valid UTF-8 sequence.
Jon Skeet
ah ok now i get the problem...yeah forget this answer :)
smeg4brains
But wouldn't the key be encoded using the system default charset when one calls getEncoded()? then you could just call key.getBytes() and new String(byte[] data)...
smeg4brains
@smeg4brains: What makes you think that? Secret keys don't have to have any text representation at all, as far as I'm aware.
Jon Skeet
@smeg4brains - some encodings (US-ASCII, for example) don't map a character to every value in the range from 0–255. When the decoder finds a byte outside this domain, it substitutes a "replacement character" (�) When the `String` is encoded to bytes again, they won't be the original bytes.
erickson
@erickson: thx for the clarification
smeg4brains
@smeg4brains: Well, with your userid you do lower expectations :)
GregS
+1 because without your post I wouldn't know why is a bad idea.. :)
Tom Brito
+2  A: 

Use a base-64 encoding to safely convert arbitrary binary data to a string and back.

The Apache Commons Codec library provides code for this, as do various others. (I'm not terribly keen on the API to Apache Commons Codec, admittedly. I don't know of any other libraries with this functionality off hand but I'm sure they exist.)

EDIT: This project provides a single file for fast encoding and decoding, and has sane method signatures - as well as plenty of extra options should you need them.

Alternatively, if the point is to save it to a database, why not just use an appropriate binary data field (Image/Blob or whatever applies to your database) rather than storing it as a string in the first place?

Jon Skeet
its becouse I'm new to DB, and I'm using Derby and hard coded SQL (which takes String as parameter). A may work better with DBs with time.. :)
Tom Brito
@Tom: I would try to fix the SQL rather than converting to a string, unless Derby doesn't support binary data. Base64 would certainly work if you really *really* want to stick to a string though.
Jon Skeet
Derby sql statments support only Strings as arguments, I choose to decode with the Apache Commons Code Hex class, as I am already some familiar with it.
Tom Brito
I'm a fan of the iharder solution also.
GregS
+3  A: 

javax.crypto.SecretKey is binary data, so you can't convert it directly to a String. You can encode it as a hex string or in Base64.

See Apache Commons Codec.

Update: If you dont want to depend on third-party libraries (and can't/don't want to store plain binary data, as Jon suggests) you can do some ad-hoc encoding, for example, following erickson's suggestion:

public static String bytesToString(byte[] b) {
    byte[] b2 = new byte[b.length + 1];
    b2[0] = 1;
    System.arraycopy(b, 0, b2, 1, b.length);
    return new BigInteger(b2).toString(Character.MAX_RADIX);
}

public static byte[] stringToBytes(String s) {
    byte[] b2 = new BigInteger(s, Character.MAX_RADIX).toByteArray();
    return Arrays.copyOfRange(b2, 1, b2.length);
}

It's rather, ugly, non-standard and not very compact. But can be practical, specially if your data is small sized.

leonbloy
A nice solution, it even accounts for leading zeros. If I may nitpick, I would choose my own constant, probably 32 or 16, rather than Character.MAX_RADIX. It's possible that some future version of java.lang.Character will use a larger value than 36.
GregS
@GregS: you are right. BTW, Character.MAX_RADIX is 36 presently
leonbloy
@leonbloy Although the answer is correct and working, I'm wondering: what you mean with "SecretKey is binary data"? For me, at some level, everything is binary data.. Could you give an example of data that isn't binary data? Thanks!
Tom Brito
@Tom: it just depends on what level the concept is defined. For example, when we speak of a HTML page, a JSON piece of data, a Java String, a pattern in a regular expression, a web.xml file, etc, we deal with some syntax that makes sense in the textual world (one could also specify some way -encoding- to represent is a binary, but that would fall outside the definition -and frequently one has freedom to use several binary encodings)..
leonbloy
+1  A: 

How about encoding as a number instead of a character string? It's not quite as compact as Base-64, but you can leave out Apache Commons.

/* Store the key. */
BigInteger n = new BigInteger(1, key.getEncoded()); /* Store as NUMBER, or … */
String s = n.toString(32);                          /* … store as VARCHAR. */

/* Reconstruct the key (may need to pad most significant bytes with zero). */
BigInteger n = new BigInteger(s); /* Load from VARCHAR, or … */
byte[] raw = n.toByte();          /* … load from NUMBER. */
byte[] original = new byte[16];
System.arraycopy(raw, 0, original, 16 - raw.length, raw.length);
erickson
Sensible idea, in some situations (I've used it once). I made some corrections (for example, this code does not keep zero bytes at the beginning, I think) and added to my post.
leonbloy
It restores zero bytes at the beginning by copying the value into the least significant bytes of the destination array.
erickson