Skip to content

add v3 impl compatible to UVF draft #51

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

Draft
wants to merge 32 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b07e43b
add v3 impl with UVF compatible file header and hardcoded key id
overheadhunter Feb 9, 2024
f35f9ac
Merge branch 'develop' into feature/uvf-draft
overheadhunter Nov 2, 2024
f47b27b
split Masterkey API into Perpetual + Revolving
overheadhunter Nov 29, 2024
f07ef0e
allow empty chunks, so UVF's EOF-chunks can be added
overheadhunter Nov 29, 2024
56dc34e
java 8 api sucks...
overheadhunter Nov 29, 2024
dd3ac84
fixed test after changing f07ef0e
overheadhunter Nov 29, 2024
485a7bb
fix javadoc
overheadhunter Dec 5, 2024
084e78a
added primitives for file name encryption
overheadhunter Dec 6, 2024
2445d1c
allow encrypting empty chunks
overheadhunter Jan 10, 2025
3924abc
Merge branch 'develop' into feature/uvf-draft
overheadhunter Jan 10, 2025
940857f
allow empty chunks (third attempt)
overheadhunter Jan 11, 2025
4db62e9
fix UVF file header
overheadhunter Jan 17, 2025
e8aeec4
use same test vectors as in typescript impl
overheadhunter Jan 17, 2025
47a26a2
fix build with Java 8
overheadhunter Jan 18, 2025
1170de4
Merge branch 'develop' into feature/uvf-draft
overheadhunter Jan 24, 2025
dcea94d
Introduce new `DirectoryContentCryptor` API
overheadhunter Jan 24, 2025
dcc1aa0
Merge branch 'develop' into feature/uvf-draft
overheadhunter Jan 24, 2025
361b3b0
typo
overheadhunter Mar 5, 2025
1e9bd32
UVF: use 64 bit keys for HMAC-SHA256
overheadhunter Mar 6, 2025
4fa5861
remove generic types
overheadhunter Mar 7, 2025
a431cf4
cleanup
overheadhunter Mar 7, 2025
688845d
API: allow file encryption w/ specific revision
overheadhunter Mar 7, 2025
8865144
API: add `Masterkey.rootDirId()`
overheadhunter Mar 7, 2025
d41b6e7
add convenience method `dirPath(dirUvfMetadata)`
overheadhunter Mar 7, 2025
d8c567b
add test to generate reference directory structure
overheadhunter Mar 7, 2025
f2745ea
fix missing `flush` before returning ciphertext
overheadhunter Mar 12, 2025
fd8ac29
Merge branch 'develop' into feature/uvf-draft
overheadhunter Mar 14, 2025
3c29fb6
implement `DirectoryContentCryptor` API for v1/v2
overheadhunter Mar 28, 2025
28dfcaa
Merge branch 'develop' into feature/uvf-draft
overheadhunter Mar 28, 2025
ad924b1
Merge branch 'develop' into feature/uvf-draft
overheadhunter Mar 28, 2025
030e3e4
use base64url in `vault.uvf` file
overheadhunter Apr 3, 2025
767b088
Merge branch 'develop' into feature/uvf-draft
overheadhunter Apr 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 24 additions & 13 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
<maven.compiler.release>8</maven.compiler.release>

<!-- dependencies -->
<gson.version>2.12.1</gson.version>
<guava.version>33.4.0-jre</guava.version>
<gson.version>2.11.0</gson.version>
<guava.version>33.3.0-jre</guava.version>
<siv-mode.version>1.6.1</siv-mode.version>
<bouncycastle.version>1.80</bouncycastle.version>
<slf4j.version>2.0.17</slf4j.version>
<bouncycastle.version>1.78.1</bouncycastle.version>
<slf4j.version>2.0.16</slf4j.version>

<!-- test dependencies -->
<junit.jupiter.version>5.12.0</junit.jupiter.version>
<mockito.version>5.15.2</mockito.version>
<junit.jupiter.version>5.11.0</junit.jupiter.version>
<mockito.version>5.13.0</mockito.version>
<hamcrest.version>3.0</hamcrest.version>
<jmh.version>1.37</jmh.version>

Expand Down Expand Up @@ -151,7 +151,7 @@
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<version>3.13.0</version>
<configuration>
<encoding>UTF-8</encoding>
<showWarnings>true</showWarnings>
Expand Down Expand Up @@ -397,7 +397,10 @@
<goal>sign</goal>
</goals>
<configuration>
<signer>bc</signer>
<gpgArguments>
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
</configuration>
</execution>
</executions>
Expand All @@ -408,16 +411,24 @@

<profile>
<id>deploy-central</id>
<distributionManagement>
<repository>
<id>ossrh</id>
<name>Maven Central</name>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>${central-publishing.version}</version>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>${nexus-staging.version}</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
<serverId>ossrh</serverId>
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
Expand Down
43 changes: 35 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,50 @@
/*******************************************************************************
* 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 headers.
* @param revision The revision of the seed to {@link RevolvingMasterkey#subKey(int, int, byte[], String) derive subkeys}.
* @return utility for encrypting and decrypting file headers
* @apiNote Only relevant for Universal Vault Format, for Cryptomator Vault Format see {@link #fileHeaderCryptor()}
*/
FileHeaderCryptor fileHeaderCryptor(int revision);

/**
* 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);

/**
* High-Level API for file name encryption and decryption
* @return utility for encryption and decryption of file names in the context of a directory
*/
DirectoryContentCryptor directoryContentCryptor();

@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
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.cryptomator.cryptolib.api;

public interface DirectoryContentCryptor {

DirectoryMetadata rootDirectoryMetadata();

DirectoryMetadata newDirectoryMetadata();

/**
* Decrypts the given directory metadata.
*
* @param ciphertext The encrypted directory metadata to decrypt.
* @return The decrypted directory metadata.
* @throws AuthenticationFailedException If the ciphertext is unauthentic.
*/
DirectoryMetadata decryptDirectoryMetadata(byte[] ciphertext) throws AuthenticationFailedException;

/**
* Encrypts the given directory metadata.
*
* @param directoryMetadata The directory metadata to encrypt.
* @return The encrypted directory metadata.
*/
byte[] encryptDirectoryMetadata(DirectoryMetadata directoryMetadata);

/**
* Computes the directory path for the given directory metadata.
* @param directoryMetadata The directory metadata.
* @return A path relative to the vault's root (i.e. starting with `d/`).
* @apiNote The path contains "/" as separator and does neither start nor end with a "/".
*/
String dirPath(DirectoryMetadata directoryMetadata);

Decrypting fileNameDecryptor(DirectoryMetadata directoryMetadata);

Encrypting fileNameEncryptor(DirectoryMetadata directoryMetadata);

@FunctionalInterface
interface Decrypting {
/**
* Decrypts a single filename
*
* @param ciphertext the full filename to decrypt, including the file extension
* @return Plaintext
* @throws AuthenticationFailedException If the ciphertext is unauthentic.
* @throws IllegalArgumentException If the filename does not meet the expected format.
*/
String decrypt(String ciphertext) throws AuthenticationFailedException, IllegalArgumentException;
}

@FunctionalInterface
interface Encrypting {
/**
* Encrypts a single filename
*
* @param plaintext the full filename to encrypt, including the file extension
* @return Ciphertext
*/
String encrypt(String plaintext);
}

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

public interface DirectoryMetadata {
}
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
63 changes: 22 additions & 41 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,50 @@
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());
}

/**
* Get the encryption subkey.
*
* @return A new copy of the subkey used for encryption
* Returns the immutable directory ID of the root directory. This ID is unique for each vault and deterministically depends on the masterkey.
* @return a unique but deterministic byte sequence
*/
public DestroyableSecretKey getEncKey() {
return new DestroyableSecretKey(getEncoded(), 0, SUBKEY_LEN_BYTES, ENC_ALG);
}
byte[] rootDirId();

@Override
void destroy();

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

}
Loading
Loading