Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add v3 impl compatible to UVF draft #51

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<!-- dependencies -->
<gson.version>2.11.0</gson.version>
<guava.version>33.2.1-jre</guava.version>
<siv-mode.version>1.5.2</siv-mode.version>
<siv-mode.version>1.6.0</siv-mode.version>
<bouncycastle.version>1.78.1</bouncycastle.version>
<slf4j.version>2.0.13</slf4j.version>

Expand Down
29 changes: 21 additions & 8 deletions src/main/java/org/cryptomator/cryptolib/api/Cryptor.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE.txt.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.cryptolib.api;

import javax.security.auth.Destroyable;

public interface Cryptor extends Destroyable, AutoCloseable {

/**
* Encryption and decryption of file content.
* @return utility for encrypting and decrypting file content
*/
FileContentCryptor fileContentCryptor();

/**
* Encryption and decryption of file headers.
* @return utility for encrypting and decrypting file headers
*/
FileHeaderCryptor fileHeaderCryptor();

/**
* Encryption and decryption of file names in Cryptomator Vault Format.
* @return utility for encrypting and decrypting file names
* @apiNote Only relevant for Cryptomator Vault Format, for Universal Vault Format see {@link #fileNameCryptor(int)}
*/
FileNameCryptor fileNameCryptor();

/**
* Encryption and decryption of file names in Universal Vault Format.
* @param revision The revision of the seed to {@link RevolvingMasterkey#subKey(int, int, byte[], String) derive subkeys}.
* @return utility for encrypting and decrypting file names
* @apiNote Only relevant for Universal Vault Format, for Cryptomator Vault Format see {@link #fileNameCryptor()}
*/
FileNameCryptor fileNameCryptor(int revision);

@Override
void destroy();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ enum Scheme {
* AES-SIV for file name encryption
* AES-GCM for content encryption
*/
SIV_GCM
SIV_GCM,

/**
* Experimental implementation of UVF draft
* @deprecated may be removed any time
* @see <a href="https://github.com/encryption-alliance/unified-vault-format">UVF</a>
*/
@Deprecated
UVF_DRAFT,
}

/**
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/org/cryptomator/cryptolib/api/FileNameCryptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import com.google.common.io.BaseEncoding;

import java.nio.charset.StandardCharsets;

/**
* Provides deterministic encryption capabilities as filenames must not change on subsequent encryption attempts,
* otherwise each change results in major directory structure changes which would be a terrible idea for cloud storage encryption.
Expand All @@ -18,11 +20,23 @@
*/
public interface FileNameCryptor {

/**
* @param cleartextDirectoryIdStr a UTF-8-encoded arbitrary directory id to be passed to one-way hash function
* @return constant length string, that is unlikely to collide with any other name.
* @apiNote Only relevant for Cryptomator Vault Format, not for Universal Vault Format
* @deprecated Use {@link #hashDirectoryId(byte[])} instead
*/
@Deprecated
default String hashDirectoryId(String cleartextDirectoryIdStr) {
return hashDirectoryId(cleartextDirectoryIdStr.getBytes(StandardCharsets.UTF_8));
}

/**
* @param cleartextDirectoryId an arbitrary directory id to be passed to one-way hash function
* @return constant length string, that is unlikely to collide with any other name.
* @apiNote Only relevant for Cryptomator Vault Format, not for Universal Vault Format
*/
String hashDirectoryId(String cleartextDirectoryId);
String hashDirectoryId(byte[] cleartextDirectoryId);

/**
* @param encoding Encoding to use to encode the returned ciphertext
Expand Down
59 changes: 17 additions & 42 deletions src/main/java/org/cryptomator/cryptolib/api/Masterkey.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,44 @@
import com.google.common.base.Preconditions;
import org.cryptomator.cryptolib.common.DestroyableSecretKey;

import javax.security.auth.Destroyable;
import java.security.SecureRandom;
import java.util.Arrays;

public class Masterkey extends DestroyableSecretKey {
public interface Masterkey extends Destroyable, AutoCloseable {

private static final String KEY_ALGORITHM = "MASTERKEY";
public static final String ENC_ALG = "AES";
public static final String MAC_ALG = "HmacSHA256";
public static final int SUBKEY_LEN_BYTES = 32;

public Masterkey(byte[] key) {
super(checkKeyLength(key), KEY_ALGORITHM);
}

private static byte[] checkKeyLength(byte[] key) {
Preconditions.checkArgument(key.length == SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES, "Invalid raw key length %s", key.length);
return key;
}

public static Masterkey generate(SecureRandom csprng) {
byte[] key = new byte[SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES];
static PerpetualMasterkey generate(SecureRandom csprng) {
byte[] key = new byte[PerpetualMasterkey.SUBKEY_LEN_BYTES + PerpetualMasterkey.SUBKEY_LEN_BYTES];
try {
csprng.nextBytes(key);
return new Masterkey(key);
return new PerpetualMasterkey(key);
} finally {
Arrays.fill(key, (byte) 0x00);
}
}

public static Masterkey from(DestroyableSecretKey encKey, DestroyableSecretKey macKey) {
Preconditions.checkArgument(encKey.getEncoded().length == SUBKEY_LEN_BYTES, "Invalid key length of encKey");
Preconditions.checkArgument(macKey.getEncoded().length == SUBKEY_LEN_BYTES, "Invalid key length of macKey");
byte[] key = new byte[SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES];
static PerpetualMasterkey from(DestroyableSecretKey encKey, DestroyableSecretKey macKey) {
Preconditions.checkArgument(encKey.getEncoded().length == PerpetualMasterkey.SUBKEY_LEN_BYTES, "Invalid key length of encKey");
Preconditions.checkArgument(macKey.getEncoded().length == PerpetualMasterkey.SUBKEY_LEN_BYTES, "Invalid key length of macKey");
byte[] key = new byte[PerpetualMasterkey.SUBKEY_LEN_BYTES + PerpetualMasterkey.SUBKEY_LEN_BYTES];
try {
System.arraycopy(encKey.getEncoded(), 0, key, 0, SUBKEY_LEN_BYTES);
System.arraycopy(macKey.getEncoded(), 0, key, SUBKEY_LEN_BYTES, SUBKEY_LEN_BYTES);
return new Masterkey(key);
System.arraycopy(encKey.getEncoded(), 0, key, 0, PerpetualMasterkey.SUBKEY_LEN_BYTES);
System.arraycopy(macKey.getEncoded(), 0, key, PerpetualMasterkey.SUBKEY_LEN_BYTES, PerpetualMasterkey.SUBKEY_LEN_BYTES);
return new PerpetualMasterkey(key);
} finally {
Arrays.fill(key, (byte) 0x00);
}
}

@Override
public Masterkey copy() {
return new Masterkey(getEncoded());
}
void destroy();

/**
* Get the encryption subkey.
*
* @return A new copy of the subkey used for encryption
* Same as {@link #destroy()}
*/
public DestroyableSecretKey getEncKey() {
return new DestroyableSecretKey(getEncoded(), 0, SUBKEY_LEN_BYTES, ENC_ALG);
}

/**
* Get the MAC subkey.
*
* @return A new copy of the subkey used for message authentication
*/
public DestroyableSecretKey getMacKey() {
return new DestroyableSecretKey(getEncoded(), SUBKEY_LEN_BYTES, SUBKEY_LEN_BYTES, MAC_ALG);
@Override
default void close() {
destroy();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.cryptomator.cryptolib.api;

import com.google.common.base.Preconditions;
import org.cryptomator.cryptolib.common.DestroyableSecretKey;

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;

public class PerpetualMasterkey implements Masterkey {

public static final String ENC_ALG = "AES";
public static final String MAC_ALG = "HmacSHA256";
public static final int SUBKEY_LEN_BYTES = 32;

private final transient byte[] key;
private boolean destroyed;

public PerpetualMasterkey(byte[] key) {
Preconditions.checkArgument(key.length == SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES, "Invalid raw key length %s", key.length);
this.key = new byte[key.length];
this.destroyed = false;
System.arraycopy(key, 0, this.key, 0, key.length);
}

public static PerpetualMasterkey generate(SecureRandom csprng) {
byte[] key = new byte[SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES];
try {
csprng.nextBytes(key);
return new PerpetualMasterkey(key);
} finally {
Arrays.fill(key, (byte) 0x00);
}
}

public static PerpetualMasterkey from(DestroyableSecretKey encKey, DestroyableSecretKey macKey) {
Preconditions.checkArgument(encKey.getEncoded().length == SUBKEY_LEN_BYTES, "Invalid key length of encKey");
Preconditions.checkArgument(macKey.getEncoded().length == SUBKEY_LEN_BYTES, "Invalid key length of macKey");
byte[] key = new byte[SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES];
try {
System.arraycopy(encKey.getEncoded(), 0, key, 0, SUBKEY_LEN_BYTES);
System.arraycopy(macKey.getEncoded(), 0, key, SUBKEY_LEN_BYTES, SUBKEY_LEN_BYTES);
return new PerpetualMasterkey(key);
} finally {
Arrays.fill(key, (byte) 0x00);
}
}

public Masterkey copy() {
return new PerpetualMasterkey(key);
}

/**
* Get the encryption subkey.
*
* @return A new copy of the subkey used for encryption
*/
public DestroyableSecretKey getEncKey() {
return new DestroyableSecretKey(key, 0, SUBKEY_LEN_BYTES, ENC_ALG);
}

/**
* Get the MAC subkey.
*
* @return A new copy of the subkey used for message authentication
*/
public DestroyableSecretKey getMacKey() {
return new DestroyableSecretKey(key, SUBKEY_LEN_BYTES, SUBKEY_LEN_BYTES, MAC_ALG);
}

@Override
public boolean isDestroyed() {
return destroyed;
}

@Override
public void destroy() {
Arrays.fill(key, (byte) 0x00);
destroyed = true;
}

public byte[] getEncoded() {
return key;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PerpetualMasterkey that = (PerpetualMasterkey) o;
return MessageDigest.isEqual(this.key, that.key);
}

@Override
public int hashCode() {
return Arrays.hashCode(key);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.cryptomator.cryptolib.api;

import org.cryptomator.cryptolib.common.DestroyableSecretKey;

public interface RevolvingMasterkey extends Masterkey {

/**
* Returns a subkey for the given revision and usage context.
* @param revision Key revision
* @param length Desired key length in bytes
* @param context Usage context to distinguish subkeys
* @param algorithm The name of the {@link javax.crypto.SecretKey#getAlgorithm() algorithm} associated with the generated subkey
* @return A subkey specificially for the given revision and context
*/
DestroyableSecretKey subKey(int revision, int length, byte[] context, String algorithm);

int firstRevision();

int currentRevision();
}
Loading
Loading