mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Add salt to backup header
This commit is contained in:
parent
f544b7d7b4
commit
90006e81db
@ -39,7 +39,8 @@ message DatabaseVersion {
|
||||
}
|
||||
|
||||
message Header {
|
||||
optional bytes iv = 1;
|
||||
optional bytes iv = 1;
|
||||
optional bytes salt = 2;
|
||||
}
|
||||
|
||||
message BackupFrame {
|
||||
|
@ -3388,6 +3388,16 @@ public final class BackupProtos {
|
||||
* <code>optional bytes iv = 1;</code>
|
||||
*/
|
||||
com.google.protobuf.ByteString getIv();
|
||||
|
||||
// optional bytes salt = 2;
|
||||
/**
|
||||
* <code>optional bytes salt = 2;</code>
|
||||
*/
|
||||
boolean hasSalt();
|
||||
/**
|
||||
* <code>optional bytes salt = 2;</code>
|
||||
*/
|
||||
com.google.protobuf.ByteString getSalt();
|
||||
}
|
||||
/**
|
||||
* Protobuf type {@code signal.Header}
|
||||
@ -3445,6 +3455,11 @@ public final class BackupProtos {
|
||||
iv_ = input.readBytes();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
bitField0_ |= 0x00000002;
|
||||
salt_ = input.readBytes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
|
||||
@ -3501,8 +3516,25 @@ public final class BackupProtos {
|
||||
return iv_;
|
||||
}
|
||||
|
||||
// optional bytes salt = 2;
|
||||
public static final int SALT_FIELD_NUMBER = 2;
|
||||
private com.google.protobuf.ByteString salt_;
|
||||
/**
|
||||
* <code>optional bytes salt = 2;</code>
|
||||
*/
|
||||
public boolean hasSalt() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
/**
|
||||
* <code>optional bytes salt = 2;</code>
|
||||
*/
|
||||
public com.google.protobuf.ByteString getSalt() {
|
||||
return salt_;
|
||||
}
|
||||
|
||||
private void initFields() {
|
||||
iv_ = com.google.protobuf.ByteString.EMPTY;
|
||||
salt_ = com.google.protobuf.ByteString.EMPTY;
|
||||
}
|
||||
private byte memoizedIsInitialized = -1;
|
||||
public final boolean isInitialized() {
|
||||
@ -3519,6 +3551,9 @@ public final class BackupProtos {
|
||||
if (((bitField0_ & 0x00000001) == 0x00000001)) {
|
||||
output.writeBytes(1, iv_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
output.writeBytes(2, salt_);
|
||||
}
|
||||
getUnknownFields().writeTo(output);
|
||||
}
|
||||
|
||||
@ -3532,6 +3567,10 @@ public final class BackupProtos {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(1, iv_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(2, salt_);
|
||||
}
|
||||
size += getUnknownFields().getSerializedSize();
|
||||
memoizedSerializedSize = size;
|
||||
return size;
|
||||
@ -3650,6 +3689,8 @@ public final class BackupProtos {
|
||||
super.clear();
|
||||
iv_ = com.google.protobuf.ByteString.EMPTY;
|
||||
bitField0_ = (bitField0_ & ~0x00000001);
|
||||
salt_ = com.google.protobuf.ByteString.EMPTY;
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -3682,6 +3723,10 @@ public final class BackupProtos {
|
||||
to_bitField0_ |= 0x00000001;
|
||||
}
|
||||
result.iv_ = iv_;
|
||||
if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
to_bitField0_ |= 0x00000002;
|
||||
}
|
||||
result.salt_ = salt_;
|
||||
result.bitField0_ = to_bitField0_;
|
||||
onBuilt();
|
||||
return result;
|
||||
@ -3701,6 +3746,9 @@ public final class BackupProtos {
|
||||
if (other.hasIv()) {
|
||||
setIv(other.getIv());
|
||||
}
|
||||
if (other.hasSalt()) {
|
||||
setSalt(other.getSalt());
|
||||
}
|
||||
this.mergeUnknownFields(other.getUnknownFields());
|
||||
return this;
|
||||
}
|
||||
@ -3764,6 +3812,42 @@ public final class BackupProtos {
|
||||
return this;
|
||||
}
|
||||
|
||||
// optional bytes salt = 2;
|
||||
private com.google.protobuf.ByteString salt_ = com.google.protobuf.ByteString.EMPTY;
|
||||
/**
|
||||
* <code>optional bytes salt = 2;</code>
|
||||
*/
|
||||
public boolean hasSalt() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
/**
|
||||
* <code>optional bytes salt = 2;</code>
|
||||
*/
|
||||
public com.google.protobuf.ByteString getSalt() {
|
||||
return salt_;
|
||||
}
|
||||
/**
|
||||
* <code>optional bytes salt = 2;</code>
|
||||
*/
|
||||
public Builder setSalt(com.google.protobuf.ByteString value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
bitField0_ |= 0x00000002;
|
||||
salt_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* <code>optional bytes salt = 2;</code>
|
||||
*/
|
||||
public Builder clearSalt() {
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
salt_ = getDefaultInstance().getSalt();
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(builder_scope:signal.Header)
|
||||
}
|
||||
|
||||
@ -5185,14 +5269,15 @@ public final class BackupProtos {
|
||||
"\030\001 \001(\t\022\013\n\003key\030\002 \001(\t\022\r\n\005value\030\003 \001(\t\"A\n\nAt" +
|
||||
"tachment\022\r\n\005rowId\030\001 \001(\004\022\024\n\014attachmentId\030" +
|
||||
"\002 \001(\004\022\016\n\006length\030\003 \001(\r\"\"\n\017DatabaseVersion",
|
||||
"\022\017\n\007version\030\001 \001(\r\"\024\n\006Header\022\n\n\002iv\030\001 \001(\014\"" +
|
||||
"\343\001\n\013BackupFrame\022\036\n\006header\030\001 \001(\0132\016.signal" +
|
||||
".Header\022\'\n\tstatement\030\002 \001(\0132\024.signal.SqlS" +
|
||||
"tatement\022,\n\npreference\030\003 \001(\0132\030.signal.Sh" +
|
||||
"aredPreference\022&\n\nattachment\030\004 \001(\0132\022.sig" +
|
||||
"nal.Attachment\022(\n\007version\030\005 \001(\0132\027.signal" +
|
||||
".DatabaseVersion\022\013\n\003end\030\006 \001(\010B1\n!org.tho" +
|
||||
"ughtcrime.securesms.backupB\014BackupProtos"
|
||||
"\022\017\n\007version\030\001 \001(\r\"\"\n\006Header\022\n\n\002iv\030\001 \001(\014\022" +
|
||||
"\014\n\004salt\030\002 \001(\014\"\343\001\n\013BackupFrame\022\036\n\006header\030" +
|
||||
"\001 \001(\0132\016.signal.Header\022\'\n\tstatement\030\002 \001(\013" +
|
||||
"2\024.signal.SqlStatement\022,\n\npreference\030\003 \001" +
|
||||
"(\0132\030.signal.SharedPreference\022&\n\nattachme" +
|
||||
"nt\030\004 \001(\0132\022.signal.Attachment\022(\n\007version\030" +
|
||||
"\005 \001(\0132\027.signal.DatabaseVersion\022\013\n\003end\030\006 " +
|
||||
"\001(\010B1\n!org.thoughtcrime.securesms.backup" +
|
||||
"B\014BackupProtos"
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||
@ -5234,7 +5319,7 @@ public final class BackupProtos {
|
||||
internal_static_signal_Header_fieldAccessorTable = new
|
||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||
internal_static_signal_Header_descriptor,
|
||||
new java.lang.String[] { "Iv", });
|
||||
new java.lang.String[] { "Iv", "Salt", });
|
||||
internal_static_signal_BackupFrame_descriptor =
|
||||
getDescriptor().getMessageTypes().get(5);
|
||||
internal_static_signal_BackupFrame_fieldAccessorTable = new
|
||||
|
@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.backup;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
@ -12,27 +12,29 @@ import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public abstract class FullBackupBase {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = FullBackupBase.class.getSimpleName();
|
||||
|
||||
protected static @NonNull byte[] getBackupKey(@NonNull String passphrase) {
|
||||
try {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
|
||||
static class BackupStream {
|
||||
static @NonNull byte[] getBackupKey(@NonNull String passphrase, @Nullable byte[] salt) {
|
||||
try {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
|
||||
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-512");
|
||||
byte[] input = passphrase.replace(" ", "").getBytes();
|
||||
byte[] hash = input;
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-512");
|
||||
byte[] input = passphrase.replace(" ", "").getBytes();
|
||||
byte[] hash = input;
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i=0;i<250000;i++) {
|
||||
if (i % 1000 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
|
||||
digest.update(hash);
|
||||
hash = digest.digest(input);
|
||||
for (int i=0;i<250000;i++) {
|
||||
if (i % 1000 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
|
||||
digest.update(hash);
|
||||
if (salt != null) digest.update(salt);
|
||||
hash = digest.digest(input);
|
||||
}
|
||||
|
||||
return ByteUtil.trim(hash, 32);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
Log.w(TAG, "Generated: " + (System.currentTimeMillis()- start));
|
||||
|
||||
return ByteUtil.trim(hash, 32);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,8 +63,7 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
@NonNull String passphrase)
|
||||
throws IOException
|
||||
{
|
||||
byte[] key = getBackupKey(passphrase);
|
||||
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(output, key);
|
||||
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(output, passphrase);
|
||||
outputStream.writeDatabaseVersion(input.getVersion());
|
||||
|
||||
List<String> tables = exportSchema(input, outputStream);
|
||||
@ -196,7 +195,7 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
}
|
||||
}
|
||||
|
||||
private static class BackupFrameOutputStream {
|
||||
private static class BackupFrameOutputStream extends BackupStream {
|
||||
|
||||
private final OutputStream outputStream;
|
||||
private final Cipher cipher;
|
||||
@ -208,10 +207,12 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
private byte[] iv;
|
||||
private int counter;
|
||||
|
||||
private BackupFrameOutputStream(@NonNull File output, @NonNull byte[] key) throws IOException {
|
||||
private BackupFrameOutputStream(@NonNull File output, @NonNull String passphrase) throws IOException {
|
||||
try {
|
||||
byte[] derived = new HKDFv3().deriveSecrets(key, "Backup Export".getBytes(), 64);
|
||||
byte[][] split = ByteUtil.split(derived, 32, 32);
|
||||
byte[] salt = Util.getSecretBytes(32);
|
||||
byte[] key = getBackupKey(passphrase, salt);
|
||||
byte[] derived = new HKDFv3().deriveSecrets(key, "Backup Export".getBytes(), 64);
|
||||
byte[][] split = ByteUtil.split(derived, 32, 32);
|
||||
|
||||
this.cipherKey = split[0];
|
||||
this.macKey = split[1];
|
||||
@ -224,7 +225,10 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
|
||||
mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
|
||||
|
||||
byte[] header = BackupProtos.BackupFrame.newBuilder().setHeader(BackupProtos.Header.newBuilder().setIv(ByteString.copyFrom(iv))).build().toByteArray();
|
||||
byte[] header = BackupProtos.BackupFrame.newBuilder().setHeader(BackupProtos.Header.newBuilder()
|
||||
.setIv(ByteString.copyFrom(iv))
|
||||
.setSalt(ByteString.copyFrom(salt)))
|
||||
.build().toByteArray();
|
||||
|
||||
outputStream.write(Conversions.intToByteArray(header.length));
|
||||
outputStream.write(header);
|
||||
|
@ -6,12 +6,8 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
@ -57,8 +53,7 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
@NonNull SQLiteDatabase db, @NonNull File file, @NonNull String passphrase)
|
||||
throws IOException
|
||||
{
|
||||
byte[] key = getBackupKey(passphrase);
|
||||
BackupRecordInputStream inputStream = new BackupRecordInputStream(file, key);
|
||||
BackupRecordInputStream inputStream = new BackupRecordInputStream(file, passphrase);
|
||||
int count = 0;
|
||||
|
||||
try {
|
||||
@ -128,7 +123,7 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
preferences.edit().putString(preference.getKey(), preference.getValue()).commit();
|
||||
}
|
||||
|
||||
private static class BackupRecordInputStream {
|
||||
private static class BackupRecordInputStream extends BackupStream {
|
||||
|
||||
private final InputStream in;
|
||||
private final Cipher cipher;
|
||||
@ -140,18 +135,9 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
private byte[] iv;
|
||||
private int counter;
|
||||
|
||||
private BackupRecordInputStream(@NonNull File file, @NonNull byte[] key) throws IOException {
|
||||
private BackupRecordInputStream(@NonNull File file, @NonNull String passphrase) throws IOException {
|
||||
try {
|
||||
byte[] derived = new HKDFv3().deriveSecrets(key, "Backup Export".getBytes(), 64);
|
||||
byte[][] split = ByteUtil.split(derived, 32, 32);
|
||||
|
||||
this.cipherKey = split[0];
|
||||
this.macKey = split[1];
|
||||
|
||||
this.cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
this.mac = Mac.getInstance("HmacSHA256");
|
||||
this.in = new FileInputStream(file);
|
||||
this.mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
|
||||
|
||||
byte[] headerLengthBytes = new byte[4];
|
||||
Util.readFully(in, headerLengthBytes);
|
||||
@ -174,6 +160,17 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
throw new IOException("Invalid IV length!");
|
||||
}
|
||||
|
||||
byte[] key = getBackupKey(passphrase, header.hasSalt() ? header.getSalt().toByteArray() : null);
|
||||
byte[] derived = new HKDFv3().deriveSecrets(key, "Backup Export".getBytes(), 64);
|
||||
byte[][] split = ByteUtil.split(derived, 32, 32);
|
||||
|
||||
this.cipherKey = split[0];
|
||||
this.macKey = split[1];
|
||||
|
||||
this.cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
this.mac = Mac.getInstance("HmacSHA256");
|
||||
this.mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
|
||||
|
||||
this.counter = Conversions.byteArrayToInt(iv);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
|
Loading…
Reference in New Issue
Block a user