CryFS volume creation & bug fixes

This commit is contained in:
Matéo Duparc 2022-06-19 17:59:24 +02:00
parent b5a8b02c5c
commit b2ab69c8f2
20 changed files with 194 additions and 113 deletions

View File

@ -379,11 +379,12 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private fun getOutputPath(isVideo: Boolean): String {
val baseName = if (isVideo) {"VID"} else {"IMG"}+'_'+dateFormat.format(Date())+'_'
var fileName: String
var outputPath: String
do {
fileName = baseName+(random.nextInt(fileNameRandomMax-fileNameRandomMin)+fileNameRandomMin)+'.'+ if (isVideo) {"mp4"} else {"jpg"}
} while (encryptedVolume.pathExists(fileName))
return PathUtils.pathJoin(outputDirectory, fileName)
val fileName = baseName+(random.nextInt(fileNameRandomMax-fileNameRandomMin)+fileNameRandomMin)+'.'+ if (isVideo) {"mp4"} else {"jpg"}
outputPath = PathUtils.pathJoin(outputDirectory, fileName)
} while (encryptedVolume.pathExists(outputPath))
return outputPath
}
private fun startTimerThen(action: () -> Unit) {
@ -447,18 +448,18 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
isRecording = false
} else if (!isWaitingForTimer) {
val path = getOutputPath(true)
/*startTimerThen {
val handleId = encryptedVolume.openWriteMode(path)
startTimerThen {
val fileHandle = encryptedVolume.openFile(path)
videoCapture?.startRecording(VideoCapture.OutputFileOptions(object : SeekableWriter {
var offset = 0L
override fun write(byteArray: ByteArray) {
offset += encryptedVolume.writeFile(handleId, offset, byteArray, byteArray.size)
offset += encryptedVolume.write(fileHandle, offset, byteArray, byteArray.size)
}
override fun seek(offset: Long) {
this.offset = offset
}
override fun close() {
encryptedVolume.closeFile(handleId)
encryptedVolume.closeFile(fileHandle)
}
}), executor, object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved() {
@ -473,7 +474,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
})
binding.recordVideoButton.setImageResource(R.drawable.stop_recording_video_button)
isRecording = true
}*/
}
}
}

View File

@ -6,6 +6,7 @@ import java.io.File
object ConstValues {
const val CREATOR = "DroidFS"
const val VOLUME_DATABASE_NAME = "SavedVolumes"
const val CRYFS_LOCAL_STATE_DIR = "cryfsLocalState"
const val SORT_ORDER_KEY = "sort_order"
val FAKE_URI: Uri = Uri.parse("fakeuri://droidfs")
const val MAX_KERNEL_WRITE = 128*1024

View File

@ -26,8 +26,8 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
const val ScryptDefaultLogN = 16
const val DefaultBS = 4096
const val CONFIG_FILE_NAME = "gocryptfs.conf"
external fun createVolume(root_cipher_dir: String, password: CharArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean
external fun nativeInit(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
external fun createVolume(root_cipher_dir: String, password: ByteArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean
private external fun nativeInit(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
external fun changePassword(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean
fun init(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): GocryptfsVolume? {

View File

@ -32,6 +32,7 @@ import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
import sushi.hardcore.droidfs.file_operations.FileOperationService
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import sushi.hardcore.droidfs.widgets.EditTextDialog
import java.io.File
@ -342,7 +343,10 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
val onlyOneAndWriteable =
onlyOneSelected &&
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].canWrite(filesDir.path)
menu.findItem(R.id.change_password).isVisible = onlyOneAndWriteable
menu.findItem(R.id.change_password).isVisible =
onlyOneAndWriteable &&
// Only gocryptfs volumes support password change
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE
menu.findItem(R.id.remove_default_open).isVisible =
onlyOneSelected &&
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].name == defaultVolumeName
@ -513,20 +517,18 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
apply()
}
}
val password = CharArray(dialogBinding.editPassword.text.length)
dialogBinding.editPassword.text.getChars(0, password.size, password, 0)
// openVolumeWithPassword is responsible for wiping the password
openVolumeWithPassword(
volume,
position,
StandardCharsets.UTF_8.encode(CharBuffer.wrap(password)).array(),
WidgetUtil.editTextContentEncode(dialogBinding.editPassword),
dialogBinding.checkboxSavePassword.isChecked,
)
}
private fun askForPassword(volume: SavedVolume, position: Int, savePasswordHash: Boolean = false) {
val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater)
if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null) {
if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null || volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE) {
dialogBinding.checkboxSavePassword.visibility = View.GONE
} else {
dialogBinding.checkboxSavePassword.isChecked = savePasswordHash

View File

@ -15,7 +15,9 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import sushi.hardcore.droidfs.*
import sushi.hardcore.droidfs.databinding.FragmentCreateVolumeBinding
import sushi.hardcore.droidfs.filesystems.CryfsVolume
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File
import java.util.*
@ -80,6 +82,37 @@ class CreateVolumeFragment: Fragment() {
if (!usfFingerprint || fingerprintProtector == null) {
binding.checkboxSavePassword.visibility = View.GONE
}
binding.spinnerVolumeType.adapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_item,
resources.getStringArray(R.array.volume_types)
).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
val encryptionCipherAdapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_item,
resources.getStringArray(R.array.gocryptfs_encryption_ciphers).toMutableList()
).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
binding.spinnerVolumeType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val ciphersArray = if (position == 0) { // Gocryptfs
binding.checkboxSavePassword.visibility = View.VISIBLE
R.array.gocryptfs_encryption_ciphers
} else {
binding.checkboxSavePassword.visibility = View.GONE
R.array.cryfs_encryption_ciphers
}
with(encryptionCipherAdapter) {
clear()
addAll(resources.getStringArray(ciphersArray).asList())
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
binding.spinnerCipher.adapter = encryptionCipherAdapter
if (pinPasswords) {
arrayOf(binding.editPassword, binding.editPasswordConfirm).forEach {
it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
@ -89,24 +122,6 @@ class CreateVolumeFragment: Fragment() {
createVolume()
true
}
binding.spinnerXchacha.adapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_item,
resources.getStringArray(R.array.encryption_cipher)
).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
binding.spinnerXchacha.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (position == 1)
CustomAlertDialogBuilder(requireContext(), themeValue)
.setTitle(R.string.warning)
.setMessage(R.string.xchacha_warning)
.setPositiveButton(R.string.ok, null)
.show()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
binding.buttonCreate.setOnClickListener {
createVolume()
}
@ -117,42 +132,10 @@ class CreateVolumeFragment: Fragment() {
(activity as AddVolumeActivity).onFragmentLoaded(false)
}
private fun createVolume() {
val password = CharArray(binding.editPassword.text.length)
binding.editPassword.text.getChars(0, password.size, password, 0)
val passwordConfirm = CharArray(binding.editPasswordConfirm.text.length)
binding.editPasswordConfirm.text.getChars(0, passwordConfirm.size, passwordConfirm, 0)
if (!password.contentEquals(passwordConfirm)) {
Toast.makeText(requireContext(), R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
Arrays.fill(password, 0.toChar())
Arrays.fill(passwordConfirm, 0.toChar())
} else {
Arrays.fill(passwordConfirm, 0.toChar())
var returnedHash: ByteArray? = null
if (binding.checkboxSavePassword.isChecked)
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
object: LoadingTask<SavedVolume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
override suspend fun doTask(): SavedVolume? {
val xchacha = when (binding.spinnerXchacha.selectedItemPosition) {
0 -> 0
1 -> 1
else -> -1
}
val volumeFile = File(volumePath)
if (!volumeFile.exists())
volumeFile.mkdirs()
val volume = if (GocryptfsVolume.createVolume(
volumePath,
password,
false,
xchacha,
GocryptfsVolume.ScryptDefaultLogN,
ConstValues.CREATOR,
returnedHash
)
) {
private fun saveVolume(success: Boolean, volumeType: Byte): SavedVolume? {
return if (success) {
val volumeName = if (isHiddenVolume) File(volumePath).name else volumePath
val volume = SavedVolume(volumeName, isHiddenVolume, EncryptedVolume.GOCRYPTFS_VOLUME_TYPE)
val volume = SavedVolume(volumeName, isHiddenVolume, volumeType)
volumeDatabase.apply {
if (isVolumeSaved(volumeName, isHiddenVolume)) // cleaning old saved path
removeVolume(volumeName)
@ -162,7 +145,49 @@ class CreateVolumeFragment: Fragment() {
} else {
null
}
Arrays.fill(password, 0.toChar())
}
private fun createVolume() {
val password = WidgetUtil.editTextContentEncode(binding.editPassword)
val passwordConfirm = WidgetUtil.editTextContentEncode(binding.editPasswordConfirm)
if (!password.contentEquals(passwordConfirm)) {
Toast.makeText(requireContext(), R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
Arrays.fill(password, 0)
Arrays.fill(passwordConfirm, 0)
} else {
Arrays.fill(passwordConfirm, 0)
var returnedHash: ByteArray? = null
if (binding.checkboxSavePassword.isChecked)
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
object: LoadingTask<SavedVolume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
override suspend fun doTask(): SavedVolume? {
val volumeFile = File(volumePath)
if (!volumeFile.exists())
volumeFile.mkdirs()
val volume = if (binding.spinnerVolumeType.selectedItem == 0) { // Gocryptfs
val xchacha = when (binding.spinnerCipher.selectedItemPosition) {
0 -> 0
1 -> 1
else -> -1
}
saveVolume(GocryptfsVolume.createVolume(
volumePath,
password,
false,
xchacha,
GocryptfsVolume.ScryptDefaultLogN,
ConstValues.CREATOR,
returnedHash
), EncryptedVolume.GOCRYPTFS_VOLUME_TYPE)
} else {
saveVolume(CryfsVolume.create(
volumePath,
CryfsVolume.getLocalStateDir(activity.filesDir.path),
password,
resources.getStringArray(R.array.cryfs_encryption_ciphers)[binding.spinnerCipher.selectedItemPosition]
), EncryptedVolume.CRYFS_VOLUME_TYPE)
}
Arrays.fill(password, 0)
return volume
}
}.startTask(lifecycleScope) { volume ->

View File

@ -454,7 +454,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
items.clear()
break
} else {
items.add(OperationFile(PathUtils.pathJoin(fileName, currentDirectoryPath), Stat.S_IFREG))
items.add(OperationFile(PathUtils.pathJoin(currentDirectoryPath, fileName), Stat.S_IFREG))
}
}
if (items.size > 0) {

View File

@ -1,7 +1,10 @@
package sushi.hardcore.droidfs.filesystems
import android.os.Parcel
import android.util.Log
import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.PathUtils
class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
companion object {
@ -11,7 +14,13 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
const val CONFIG_FILE_NAME = "cryfs.config"
private external fun nativeInit(baseDir: String, localStateDir: String, password: ByteArray): Long
private external fun nativeInit(
baseDir: String,
localStateDir: String,
password: ByteArray,
createBaseDir: Boolean,
cipher: String?
): Long
private external fun nativeCreate(fusePtr: Long, path: String, mode: Int): Long
private external fun nativeOpen(fusePtr: Long, path: String, flags: Int): Long
private external fun nativeRead(fusePtr: Long, fileHandle: Long, buffer: ByteArray, offset: Long): Int
@ -27,9 +36,25 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
private external fun nativeClose(fusePtr: Long)
private external fun nativeIsClosed(fusePtr: Long): Boolean
fun init(baseDir: String, localStateDir: String, password: ByteArray): CryfsVolume {
val fusePtr = nativeInit(baseDir, localStateDir, password)
return CryfsVolume(fusePtr)
fun getLocalStateDir(filesDir: String): String {
return PathUtils.pathJoin(filesDir, ConstValues.CRYFS_LOCAL_STATE_DIR)
}
private fun init(baseDir: String, localStateDir: String, password: ByteArray, createBaseDir: Boolean, cipher: String?): CryfsVolume? {
val fusePtr = nativeInit(baseDir, localStateDir, password, createBaseDir, cipher)
return if (fusePtr == 0L) {
null
} else {
CryfsVolume(fusePtr)
}
}
fun create(baseDir: String, localStateDir: String, password: ByteArray, cipher: String?): Boolean {
return init(baseDir, localStateDir, password, true, cipher)?.also { it.close() } != null
}
fun init(baseDir: String, localStateDir: String, password: ByteArray): CryfsVolume? {
return init(baseDir, localStateDir, password, false, null)
}
}

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.GocryptfsVolume
import sushi.hardcore.droidfs.SavedVolume
import sushi.hardcore.droidfs.explorers.ExplorerElement
@ -46,7 +47,7 @@ abstract class EncryptedVolume: Parcelable {
GocryptfsVolume.init(volume.getFullPath(filesDir), password, givenHash, returnedHash)
}
CRYFS_VOLUME_TYPE -> {
CryfsVolume.init(volume.getFullPath(filesDir), PathUtils.pathJoin(filesDir, "localState"), password!!)
CryfsVolume.init(volume.getFullPath(filesDir), CryfsVolume.getLocalStateDir(filesDir), password!!)
}
else -> throw invalidVolumeType()
}

View File

@ -14,19 +14,20 @@ import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File
import java.text.DecimalFormat
import kotlin.math.log10
import kotlin.math.max
import kotlin.math.pow
object PathUtils {
fun getParentPath(path: String): String {
return if (path.endsWith("/")) {
val a = path.substring(0, path.length - 2)
val a = path.substring(0, max(1, path.length - 1))
if (a.count { it == '/' } == 1) {
"/"
} else {
a.substring(0, a.lastIndexOf("/"))
}
} else {
if (path.count { it == '/' } == 1) {
if (path.count { it == '/' } <= 1) {
"/"
} else {
path.substring(0, path.lastIndexOf("/"))
@ -48,17 +49,11 @@ object PathUtils {
}
fun getRelativePath(parentPath: String, childPath: String): String {
return when {
parentPath.isEmpty() -> {
childPath
}
parentPath.length == childPath.length -> {
""
}
else -> {
childPath.substring(parentPath.length + 1)
}
}
return childPath.substring(parentPath.length + if (parentPath.endsWith("/")) {
0
} else {
1
})
}
fun isChildOf(childPath: String, parentPath: String): Boolean {

View File

@ -0,0 +1,18 @@
package sushi.hardcore.droidfs.util
import android.widget.EditText
import java.nio.CharBuffer
import java.nio.charset.StandardCharsets
import java.util.*
object WidgetUtil {
fun editTextContentEncode(editText: EditText): ByteArray {
val charArray = CharArray(editText.text.length)
editText.text.getChars(0, editText.text.length, charArray, 0)
val byteArray = StandardCharsets.UTF_8.encode(
CharBuffer.wrap(charArray)
).array()
Arrays.fill(charArray, Char.MIN_VALUE)
return byteArray
}
}

View File

@ -31,7 +31,8 @@ void jbyteArray_to_unsignedCharArray(const jbyte* src, unsigned char* dst, const
JNIEXPORT jboolean JNICALL
Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *env, jclass clazz,
jstring jroot_cipher_dir, jcharArray jpassword,
jstring jroot_cipher_dir,
jbyteArray jpassword,
jboolean plainTextNames,
jint xchacha,
jint logN,
@ -42,9 +43,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *
GoString gofilename = {root_cipher_dir, strlen(root_cipher_dir)}, gocreator = {creator, strlen(creator)};
const size_t password_len = (*env)->GetArrayLength(env, jpassword);
jchar* jchar_password = (*env)->GetCharArrayElements(env, jpassword, NULL);
char password[password_len];
jcharArray_to_charArray(jchar_password, password, password_len);
char* password = (char*)(*env)->GetByteArrayElements(env, jpassword, NULL);
GoSlice go_password = {password, password_len, password_len};
size_t returned_hash_len;
@ -65,7 +64,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *
(*env)->ReleaseStringUTFChars(env, jroot_cipher_dir, root_cipher_dir);
(*env)->ReleaseStringUTFChars(env, jcreator, creator);
wipe(password, password_len);
(*env)->ReleaseCharArrayElements(env, jpassword, jchar_password, 0);
(*env)->ReleaseByteArrayElements(env, jpassword, (jbyte*)password, 0);
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
unsignedCharArray_to_jbyteArray(returned_hash, jbyte_returned_hash, returned_hash_len);
@ -260,15 +259,11 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1list_1dir(JNIEnv *env, jobje
size_t name_len = strlen(name);
const char gcf_full_path[plain_dir_len+name_len+2];
if (plain_dir_len > 0){
strcpy(gcf_full_path, plain_dir);
if (plain_dir[-2] != '/') {
strcat(gcf_full_path, "/");
}
strcat(gcf_full_path, name);
} else {
strcpy(gcf_full_path, name);
}
GoString go_name = {gcf_full_path, strlen(gcf_full_path)};
struct gcf_get_attrs_return attrs = gcf_get_attrs(sessionID, go_name);

View File

@ -1,7 +1,7 @@
#include <sys/stat.h>
#include <jni.h>
jlong cryfs_init(JNIEnv* env, jstring jbaseDir, jstring jlocalSateDir, jbyteArray jpassword);
jlong cryfs_init(JNIEnv* env, jstring jbaseDir, jstring jlocalSateDir, jbyteArray jpassword, jboolean createBaseDir, jstring jcipher);
jlong cryfs_create(JNIEnv* env, jlong fusePtr, jstring jpath, mode_t mode);
jlong cryfs_open(JNIEnv* env, jlong fusePtr, jstring jpath, jint flags);
jint cryfs_read(JNIEnv* env, jlong fusePtr, jlong fileHandle, jbyteArray jbuffer, jlong offset);
@ -20,8 +20,9 @@ jboolean cryfs_is_closed(jlong fusePtr);
JNIEXPORT jlong JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeInit(JNIEnv *env, jobject thiz,
jstring base_dir, jstring jlocalStateDir,
jbyteArray password) {
return cryfs_init(env, base_dir, jlocalStateDir, password);
jbyteArray password, jboolean createBaseDir,
jstring cipher) {
return cryfs_init(env, base_dir, jlocalStateDir, password, createBaseDir, cipher);
}
JNIEXPORT jlong JNICALL

View File

@ -6,6 +6,18 @@
android:gravity="center_vertical"
android:layout_marginHorizontal="@dimen/volume_operation_horizontal_gap">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/volume_type_label"/>
<Spinner
android:id="@+id/spinner_volume_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginVertical="@dimen/volume_operation_vertical_gap"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -44,11 +56,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/encryption_cipher_label"
android:layout_toStartOf="@id/spinner_xchacha"
android:layout_toStartOf="@id/spinner_cipher"
android:layout_alignParentStart="true"/>
<Spinner
android:id="@+id/spinner_xchacha"
android:id="@+id/spinner_cipher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />

View File

@ -1,10 +1,4 @@
<resources>
<string-array name="encryption_cipher">
<item>AES-GCM</item>
<item>XChaCha20-Poly1305</item>
<item>@string/auto</item>
</string-array>
<string-array name="sort_orders_entries">
<item>اسم</item>
<item>حجم</item>

View File

@ -138,7 +138,6 @@
<string name="image_saved_successfully">تم حفظ تغييرات الصورة بنجاح.</string>
<string name="bitmap_compress_failed">فشل ضغط الصورة النقطية.</string>
<string name="file_write_failed">فشل كتابة الملف.</string>
<string name="error_not_a_volume">لم يتم التعرف على مجلد التشفير Gocryptfs. يرجى التحقق من المسار المحدد.</string>
<string name="version">إصدار</string>
<string name="error_cipher_null">تشفير الخطأ فارغ</string>
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
@ -173,7 +172,6 @@
<string name="maximize_quality">دقة أعلى</string>
<string name="minimize_latency">أداء أفضل</string>
<string name="auto">تلقائي</string>
<string name="xchacha_warning">XChaCha20-Poly1305 مدعوم فقط منذ إصدار gocryptfs v2.2.0. لن تتمكن الإصدارات الأقدم من فتح وحدة تخزين بناءً على هذا التشفير.</string>
<string name="encryption_cipher_label">خوارزمية التشفير:</string>
<string name="theme">سمة</string>
<string name="theme_summary">تخصيص سمة التطبيق</string>

View File

@ -138,7 +138,6 @@
<string name="image_saved_successfully">Cambios de la imagen guardados con éxito.</string>
<string name="bitmap_compress_failed">Fallo al comprimir el mapa de bits.</string>
<string name="file_write_failed">No se ha podido escribir el archivo.</string>
<string name="error_not_a_volume">No se reconoce el volumen Gocryptfs. Por favor, comprueba la ruta seleccionada.</string>
<string name="version">Versión</string>
<string name="error_cipher_null">Error de cifrado núlo</string>
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
@ -173,7 +172,6 @@
<string name="maximize_quality">Maximizar la calidad</string>
<string name="minimize_latency">Minimizar la latencia</string>
<string name="auto">Automático</string>
<string name="xchacha_warning">XChaCha20-Poly1305 sólo está soportado desde gocryptfs v2.2.0. Las versiones anteriores no podrán abrir un volumen basado en este cifrado.</string>
<string name="encryption_cipher_label">Cifrado de encriptación:</string>
<string name="theme">Tema</string>
<string name="theme_summary">Personalizar el tema de la aplicación</string>

View File

@ -135,7 +135,6 @@
<string name="image_saved_successfully">Mudanças na imagem salva com sucesso.</string>
<string name="bitmap_compress_failed">Falha ao compactar o bitmap.</string>
<string name="file_write_failed">Falha ao salvar o arquivo.</string>
<string name="error_not_a_volume">O volume do Gocryptfs não foi reconhecido. Por favor, verifique a localização selecionada.</string>
<string name="version">Versão</string>
<string name="error_cipher_null">Erro, o cipher é nulo</string>
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
@ -170,7 +169,6 @@
<string name="maximize_quality">Maximizar a qualidade</string>
<string name="minimize_latency">Minimizar a latência</string>
<string name="auto">Autom</string>
<string name="xchacha_warning">XChaCha20-Poly1305 só tem suporte desde gocryptfs v2.2.0. Versões mais antigas não podem abrir um volume baseado neste cipher.</string>
<string name="encryption_cipher_label">Cipher de criptografia:</string>
<string name="theme">Tema</string>
<string name="theme_summary">Personalizar o tema do app</string>

View File

@ -133,7 +133,6 @@
<string name="image_saved_successfully">Изменения изображения успешно сохранены.</string>
<string name="bitmap_compress_failed">Невозможно сжать растровое изображение.</string>
<string name="file_write_failed">Невозможно записать файл.</string>
<string name="error_not_a_volume">Том GocryptFS не распознан. Проверьте выбранный путь.</string>
<string name="version">Версия</string>
<string name="error_cipher_null">Шифр ошибки нулевой</string>
<string name="key_permanently_invalidated_exception_msg">Похоже, вы добавили новый отпечаток пальца. Сохранённый хеш паролей стал непригодным для использования.</string>
@ -167,7 +166,6 @@
<string name="maximize_quality">Максимальное качество</string>
<string name="minimize_latency">Минимальная задержка</string>
<string name="auto">Авто</string>
<string name="xchacha_warning">Шифр XChaCha20-Poly1305 поддерживается только с версии GocryptFS 2.2.0. Более старые версии не смогут открыть том, использующий данный шифр.</string>
<string name="encryption_cipher_label">Шифр:</string>
<string name="theme">Тема</string>
<string name="theme_summary">Настройка темы приложения</string>

View File

@ -1,10 +1,29 @@
<resources>
<string-array name="encryption_cipher">
<string-array name="volume_types">
<item>Gocryptfs</item>
<item>CryFS</item>
</string-array>
<string-array name="gocryptfs_encryption_ciphers">
<item>AES-GCM</item>
<item>XChaCha20-Poly1305</item>
<item>@string/auto</item>
</string-array>
<string-array name="cryfs_encryption_ciphers">
<item>xchacha20-poly1305</item>
<item>aes-256-gcm</item>
<item>aes-128-gcm</item>
<item>twofish-256-gcm</item>
<item>twofish-128-gcm</item>
<item>serpent-256-gcm</item>
<item>serpent-128-gcm</item>
<item>cast-256-gcm</item>
<item>mars-448-gcm</item>
<item>mars-256-gcm</item>
<item>mars-128-gcm</item>
</string-array>
<string-array name="sort_orders_entries">
<item>Name</item>
<item>Size</item>

View File

@ -138,7 +138,7 @@
<string name="image_saved_successfully">Image changes successfully saved.</string>
<string name="bitmap_compress_failed">Failed to compress the bitmap.</string>
<string name="file_write_failed">Failed to write the file.</string>
<string name="error_not_a_volume">Gocryptfs volume not recognized. Please check the selected path.</string>
<string name="error_not_a_volume">Encrypted volume not recognized. Please check the selected path.</string>
<string name="version">Version</string>
<string name="error_cipher_null">Error cipher is null</string>
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
@ -173,7 +173,6 @@
<string name="maximize_quality">Maximize quality</string>
<string name="minimize_latency">Minimize latency</string>
<string name="auto">Auto</string>
<string name="xchacha_warning">XChaCha20-Poly1305 is only supported since gocryptfs v2.2.0. Older versions won\'t be able to open a volume based on this cipher.</string>
<string name="encryption_cipher_label">Encryption cipher:</string>
<string name="theme">Theme</string>
<string name="theme_summary">Customize app theme</string>
@ -242,4 +241,5 @@
<string name="elements_selected">%d/%d selected</string>
<string name="pin_passwords_title">Numeric keypad layout</string>
<string name="pin_passwords_summary">Use a numeric keypad layout when entering volume passwords</string>
<string name="volume_type_label">Volume type:</string>
</resources>