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