Compare commits

...

33 Commits

Author SHA1 Message Date
Philipp Crocoll
2593a8548f Merge branch 'master' into 1617-use-gnu-tls-stream 2025-07-15 12:25:01 +02:00
Philipp Crocoll
d04d455fbd add missing changelog for 1.13 2025-07-15 12:00:05 +02:00
Philipp Crocoll
8a03ddb7f3 always upload files to Github release 2025-07-15 11:08:26 +02:00
Philipp Crocoll
d6ce2a32e9 allow manually triggering a release workflow run 2025-07-15 08:30:17 +02:00
Philipp Crocoll
21f1c8404c update workflows to not create signed apks during build but only in release. Create releases as drafts. 2025-07-15 08:14:52 +02:00
Philipp Crocoll
092b8689b8 fix build-artifact paths 2025-07-08 21:35:51 +02:00
Philipp Crocoll
3d3ba45cb1 extract keystore for subsequent build step 2025-07-08 18:23:02 +02:00
Philipp Crocoll
b380100307 Manifest and changelog for v1.13 2025-07-08 17:57:52 +02:00
Philipp Crocoll
4c5ddd59d8 build signed apk on every build of master branch 2025-07-08 17:57:37 +02:00
PhilippC
5ed183f318 Merge pull request #2929 from PhilippC/l10n_master3
New Crowdin updates
2025-07-08 17:56:50 +02:00
PhilippC
9c27fd3e78 Merge pull request #2938 from PhilippC/security/audit_suggestions
Security suggestions from Audit
2025-07-08 17:52:16 +02:00
Philipp Crocoll
62c361feb0 revert unintentional change 2025-07-08 17:03:32 +02:00
Philipp Crocoll
0ee2495528 disable password-based QuickUnlock when device is not protected by screen lock 2025-07-08 16:54:45 +02:00
Philipp Crocoll
7dc635a625 Update KeePass2 code for password quality estimation; add and use list of most popular passwords to account for NIST recommendation of using "blocklists" 2025-07-08 12:09:59 +02:00
Philipp Crocoll
e15112c3b4 disable cleartextTrafficPermitted. default to https for links. 2025-07-08 10:35:50 +02:00
PhilippC
1d85fffb18 New translations strings.xml (Chinese Simplified) 2025-07-07 18:44:26 +02:00
PhilippC
5e2f29e737 New translations strings.xml (Chinese Simplified) 2025-07-07 17:20:28 +02:00
Philipp Crocoll
89a09ea142 set version to 1.12-r9d 2025-07-05 14:17:07 +02:00
Philipp Crocoll
628c0d2c19 enable creation of a release 2025-07-05 14:16:17 +02:00
Philipp Crocoll
584feabe44 build process: add previously missing change; fix error in build.yml, more verbose output in release.yml 2025-07-05 13:34:01 +02:00
Philipp Crocoll
ae2cfde897 change makefile to no longer use msbuild but always dotnet 2025-07-05 13:10:21 +02:00
Philipp Crocoll
42c66670b8 release process: make find calls to run in bash 2025-07-05 12:12:49 +02:00
Philipp Crocoll
288539b902 make output more verbose 2025-07-05 11:09:55 +02:00
Philipp Crocoll
61fd32f121 avoid building the "full" apk when calling apk_split 2025-07-05 09:39:54 +02:00
Philipp Crocoll
43108ec4a6 remove ls step in release 2025-07-05 08:35:58 +02:00
PhilippC
426fbc2510 New translations strings.xml (Polish) 2025-07-04 23:10:33 +02:00
PhilippC
e89a961c02 New translations strings.xml (Polish) 2025-07-04 22:15:11 +02:00
PhilippC
766c29b7a9 New translations strings.xml (Spanish) 2025-07-03 14:39:21 +02:00
PhilippC
c4206e58bf New translations strings.xml (German) 2025-06-24 15:34:08 +02:00
PhilippC
630ededf3b New translations strings.xml (German) 2025-06-24 13:51:08 +02:00
Philipp Crocoll
ba7b02cd1e remove testing credentials 2025-04-08 15:46:38 +02:00
Philipp Crocoll
aec9441de4 update FluentFTP 2025-04-08 15:26:04 +02:00
Philipp Crocoll
5edf42254d this is an experiment to use GnuTlsStream (the ftpcredentials.xml have some hardcoded credentials for a public FTP server for testing). Unfortunately, the app restarts when loading the native libraries for GnuTLS. 2025-04-01 15:10:04 +02:00
31 changed files with 13083 additions and 2468 deletions

View File

@@ -78,7 +78,7 @@ jobs:
# - name: Build keepass2android (net) # - name: Build keepass2android (net)
# run: | # run: |
# make msbuild Flavor=Net # make dotnetbuild Flavor=Net
# - name: Build APK (net) # - name: Build APK (net)
# run: | # run: |
@@ -96,7 +96,7 @@ jobs:
# - name: Build keepass2android (nonet) # - name: Build keepass2android (nonet)
# run: | # run: |
# make msbuild Flavor=NoNet # make dotnetbuild Flavor=NoNet
# - name: Build APK (nonet) # - name: Build APK (nonet)
# run: | # run: |
@@ -212,7 +212,7 @@ jobs:
# - name: Build keepass2android (net) # - name: Build keepass2android (net)
# run: | # run: |
# make msbuild Flavor=Net # make dotnetbuild Flavor=Net
# - name: Build APK (net) # - name: Build APK (net)
# run: | # run: |
@@ -230,7 +230,7 @@ jobs:
# - name: Build keepass2android (nonet) # - name: Build keepass2android (nonet)
# run: | # run: |
# make msbuild Flavor=NoNet # make dotnetbuild Flavor=NoNet
# - name: Build APK (nonet) # - name: Build APK (nonet)
# run: | # run: |
@@ -279,7 +279,7 @@ jobs:
with: with:
minimum-size: 8GB minimum-size: 8GB
- name: Add msbuild to PATH - name: Add dotnetbuild to PATH
uses: microsoft/setup-msbuild@v2 uses: microsoft/setup-msbuild@v2
# If we want to also have nmake, use this instead # If we want to also have nmake, use this instead
#uses: ilammy/msvc-dev-cmd@v1 #uses: ilammy/msvc-dev-cmd@v1
@@ -322,18 +322,24 @@ jobs:
- name: Build keepass2android (net) - name: Build keepass2android (net)
run: | run: |
make msbuild Flavor=Net make dotnetbuild Flavor=Net
- name: Build APK (net) - name: Build APK (net)
if: github.ref == 'refs/heads/master'
env:
DropboxAppKey: ${{ secrets.DROPBOX_APP_KEY }}
DropboxAppSecret: ${{ secrets.DROPBOX_APP_SECRET }}
DropboxAppFolderAppKey: ${{ secrets.DROPBOX_APP_FOLDER_APP_KEY }}
DropboxAppFolderAppSecret: ${{ secrets.DROPBOX_APP_FOLDER_APP_SECRET }}
run: | run: |
make apk Flavor=Net make apk Configuration=Release Flavor=Net
- name: Archive production artifacts (net) - name: Archive production artifacts (net)
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: signed APK ('net' built on ${{ github.job }}) name: archive APK ('net' built on ${{ github.job }})
path: | path: |
src/keepass2android/bin/*/*-Signed.apk src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk
- name: Select the manifest - name: Select the manifest
run: | run: |
@@ -344,7 +350,7 @@ jobs:
- name: Build keepass2android (nonet) - name: Build keepass2android (nonet)
run: | run: |
make msbuild Flavor=NoNet make dotnetbuild Flavor=NoNet
- name: Test Autofill - name: Test Autofill
working-directory: ./src/Kp2aAutofillParser.Tests working-directory: ./src/Kp2aAutofillParser.Tests
@@ -357,7 +363,7 @@ jobs:
- name: Archive production artifacts (nonet) - name: Archive production artifacts (nonet)
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: signed APK ('nonet' built on ${{ github.job }}) name: archive APK ('nonet' built on ${{ github.job }})
path: | path: |
src/keepass2android/bin/*/*-Signed.apk src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk

View File

@@ -6,6 +6,7 @@ on:
push: push:
tags: tags:
- "v1.*" - "v1.*"
workflow_dispatch: # Allows manual triggering of the workflow
jobs: jobs:
build-release: build-release:
@@ -54,7 +55,7 @@ jobs:
with: with:
minimum-size: 8GB minimum-size: 8GB
- name: Add msbuild to PATH - name: Add msbuild/dotnet to PATH
uses: microsoft/setup-msbuild@v2 uses: microsoft/setup-msbuild@v2
# If we want to also have nmake, use this instead # If we want to also have nmake, use this instead
#uses: ilammy/msvc-dev-cmd@v1 #uses: ilammy/msvc-dev-cmd@v1
@@ -78,17 +79,35 @@ jobs:
run: | run: |
make java make java
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Update dotnet workloads - name: Update dotnet workloads
run: | run: |
dotnet workload update dotnet workload update
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Select the manifest - name: Select the manifest
run: | run: |
make manifestlink Flavor=${{ matrix.flavor }} make manifestlink Flavor=${{ matrix.flavor }}
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Install NuGet dependencies - name: Install NuGet dependencies
run: make nuget Flavor=${{ matrix.flavor }} run: make nuget Flavor=${{ matrix.flavor }}
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Build APK (net) - name: Build APK (net)
env: env:
KeyStore: "${{ github.workspace }}/kp2a.keystore" KeyStore: "${{ github.workspace }}/kp2a.keystore"
@@ -102,23 +121,27 @@ jobs:
run: | run: |
make ${{ matrix.target }} Configuration=Release Flavor=${{ matrix.flavor }} make ${{ matrix.target }} Configuration=Release Flavor=${{ matrix.flavor }}
- name: List files - name: List apks
run: find . -type f -name "*.apk"
shell: bash shell: bash
run: |
ls src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk
ls src/keepass2android-app/bin/Release/net8.0-android/*/publish/*.apk
- name: Archive production artifacts - name: Archive production artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: keepass2android_${{ matrix.target }}_${{ matrix.flavor }} name: keepass2android_${{ matrix.target }}_${{ matrix.flavor }}
# the first line is for "apk" target, the second line is for "apk_split" target
path: | path: |
src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk
src/keepass2android-app/bin/Release/net8.0-android/*/publish/*.apk src/keepass2android-app/bin/Release/net8.0-android/*/publish/*.apk
#- name: Upload APK to GitHub Release - name: List apks
# uses: softprops/action-gh-release@v2 run: find . -type f -name "*.apk"
# if: github.ref_type == 'tag' shell: bash
# with:
# files: | - name: Upload APK to GitHub Release
# src/keepass2android-app/bin/Release/net8.0-android/*/publish/*.apk src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk uses: softprops/action-gh-release@v2
with:
draft: true
files: |
src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk
src/keepass2android-app/bin/Release/net8.0-android/*/publish/*.apk

View File

@@ -4,10 +4,10 @@
# This Makefile can be used on both unix-like (use make) & windows (with GNU make) # This Makefile can be used on both unix-like (use make) & windows (with GNU make)
# #
# append the Configuration variable to 'make' call with value to use in '/p:Configuration=' # append the Configuration variable to 'make' call with value to use in '/p:Configuration='
# of msbuild command. # of dotnetbuild command.
# #
# append the Flavor variable to 'make' call with value to use in '/p:Flavor=' # append the Flavor variable to 'make' call with value to use in '/p:Flavor='
# of msbuild command. # of dotnetbuild command.
# #
# Example: # Example:
# make Configuration=Release Flavor=NoNet # make Configuration=Release Flavor=NoNet
@@ -18,7 +18,7 @@
# - native: build the native libs # - native: build the native libs
# - java: build the java libs # - java: build the java libs
# - nuget: restore NuGet packages # - nuget: restore NuGet packages
# - msbuild: build the project # - dotnetbuild: build the project
# - apk: same as all # - apk: same as all
# - manifestlink: creates a symlink (to be used in building) to the AndroidManifest corresponding to the selected Flavor # - manifestlink: creates a symlink (to be used in building) to the AndroidManifest corresponding to the selected Flavor
# #
@@ -27,7 +27,7 @@
# - clean_native: clean native lib # - clean_native: clean native lib
# - clean_java: call clean target of java libs # - clean_java: call clean target of java libs
# - clean_nuget: cleanup the 'nuget restore' # - clean_nuget: cleanup the 'nuget restore'
# - clean_msbuild: call clean target of msbuild # - clean_dotnet: call clean target of dotnetbuild
# #
# #
# #
@@ -60,45 +60,23 @@ $(info MAKESHELL: $(MAKESHELL))
$(info SHELL: $(SHELL)) $(info SHELL: $(SHELL))
$(info ) $(info )
# On linux use xabuild, on Windows use MSBuild.exe, otherwise (macos?) use msbuild.
ifeq ($(detected_OS),Linux) ifeq ($(detected_OS),Linux)
MSBUILD_binary := dotnet DOTNET_binary := dotnet
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary)) publish DOTNET := $(shell $(WHICH) $(DOTNET_binary))
else ifeq ($(detected_OS),Windows) else ifeq ($(detected_OS),Windows)
MSBUILD_binary := dotnet DOTNET_binary := dotnet
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary) 2> nul) publish DOTNET := $(shell $(WHICH) $(DOTNET_binary) 2> nul)
ifeq ($(MSBUILD),)
# Additional heuristic to find MSBUILD_BINARY on Windows
VSWHERE := "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
VSWHERE_CHECK := $(shell @echo off & $(VSWHERE) 2> nul || echo VSWHERE_NOT_FOUND)
ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND)
MSBUILD := $(shell @echo off & $(VSWHERE) -latest -prerelease -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe)
VS_INSTALL_PATH := $(shell @echo off & $(VSWHERE) -property installationPath)
endif
endif
else else
MSBUILD_binary := msbuild DOTNET_binary := dotnet
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary)) DOTNET := $(shell $(WHICH) $(DOTNET_binary))
endif endif
ifeq ($(MSBUILD),) ifeq ($(DOTNET),)
$(info ) $(info )
$(info '$(MSBUILD_binary)' binary could not be found. Check it is in your PATH.) $(info '$(DOTNET_binary)' binary could not be found. Check it is in your PATH.)
ifeq ($(detected_OS),Windows)
ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND)
$(info )
$(info You may retry after running in the command prompt:)
$(info )
$(info "$(VS_INSTALL_PATH)\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64)
$(info )
$(info If this doesn't work, install/find the location of vcvarsall.bat)
$(info or install and add msbuild.exe to your PATH)
$(info )
endif
endif
$(error ) $(error )
endif endif
$(info MSBUILD: $(MSBUILD)) $(info DOTNET: $(DOTNET))
$(info ) $(info )
ifeq ($(ANDROID_SDK_ROOT),) ifeq ($(ANDROID_SDK_ROOT),)
@@ -117,7 +95,7 @@ endif
$(info ANDROID_NDK_ROOT: $(ANDROID_NDK_ROOT)) $(info ANDROID_NDK_ROOT: $(ANDROID_NDK_ROOT))
ifneq ($(Configuration),) ifneq ($(Configuration),)
MSBUILD_PARAM = -p:Configuration="$(Configuration)" DOTNET_PARAM = -p:Configuration="$(Configuration)"
else else
$(warning Configuration environment variable not set.) $(warning Configuration environment variable not set.)
endif endif
@@ -127,7 +105,7 @@ CREATE_MANIFEST_LINK :=
MANIFEST_FILE := MANIFEST_FILE :=
ifneq ($(Flavor),) ifneq ($(Flavor),)
MSBUILD_PARAM += -p:Flavor="$(Flavor)" DOTNET_PARAM += -p:Flavor="$(Flavor)"
ifneq ($(Flavor),) ifneq ($(Flavor),)
ifeq ($(Flavor),Debug) ifeq ($(Flavor),Debug)
MANIFEST_FILE := AndroidManifest_debug.xml MANIFEST_FILE := AndroidManifest_debug.xml
@@ -152,7 +130,7 @@ else
endif endif
ifneq ($(KeyStore),) ifneq ($(KeyStore),)
MSBUILD_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a" DOTNET_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a"
endif endif
ifeq ($(detected_OS),Windows) ifeq ($(detected_OS),Windows)
@@ -176,7 +154,7 @@ endif
# Recursive wildcard: https://stackoverflow.com/a/18258352 # Recursive wildcard: https://stackoverflow.com/a/18258352
rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d)) rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
$(info MSBUILD_PARAM: $(MSBUILD_PARAM)) $(info DOTNET_PARAM: $(DOTNET_PARAM))
$(info nuget path: $(shell $(WHICH) nuget)) $(info nuget path: $(shell $(WHICH) nuget))
$(info ) $(info )
@@ -254,7 +232,7 @@ OUTPUT_PluginQR = src/java/Keepass2AndroidPluginSDK2/app/build/outputs/aar/Keepa
.PHONY: native $(NATIVE_COMPONENTS) clean_native $(NATIVE_CLEAN_TARGETS) \ .PHONY: native $(NATIVE_COMPONENTS) clean_native $(NATIVE_CLEAN_TARGETS) \
java $(JAVA_COMPONENTS) clean_java $(JAVA_CLEAN_TARGETS) \ java $(JAVA_COMPONENTS) clean_java $(JAVA_CLEAN_TARGETS) \
nuget clean_nuget \ nuget clean_nuget \
msbuild clean_msbuild \ dotnetbuild clean_dotnet \
apk all clean apk all clean
all: apk all: apk
@@ -303,7 +281,7 @@ ifeq ($(shell $(WHICH) nuget),)
endif endif
$(RMFILE) stamp.nuget_* $(RMFILE) stamp.nuget_*
nuget restore src/KeePass.sln nuget restore src/KeePass.sln
$(MSBUILD) src/KeePass.sln -t:restore $(MSBUILD_PARAM) -p:RestorePackagesConfig=true $(DOTNET) restore src/KeePass.sln $(DOTNET_PARAM) -p:RestorePackagesConfig=true
@echo "" > stamp.nuget_$(Flavor) @echo "" > stamp.nuget_$(Flavor)
manifestlink: manifestlink:
@@ -313,20 +291,20 @@ manifestlink:
##### #####
msbuild: manifestlink native java nuget dotnetbuild: manifestlink native java nuget
$(MSBUILD) src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(MSBUILD_PARAM) -p:Platform="Any CPU" -m $(DOTNET) build src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(DOTNET_PARAM) -p:Platform="Any CPU" -m
apk: msbuild apk: manifestlink native java nuget
$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m $(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m
apk_split: msbuild apk_split: manifestlink native java nuget
$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm $(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm
$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm64 $(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm64
$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x86 $(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x86
$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x64 $(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x64
src/build-scripts/rename-output-apks.sh src/keepass2android-app/bin/Release/net8.0-android/ src/build-scripts/rename-output-apks.sh src/keepass2android-app/bin/Release/net8.0-android/
build_all: msbuild build_all: dotnetbuild
##### Cleanup targets ##### Cleanup targets
@@ -370,10 +348,10 @@ else
endif endif
$(RMFILE) stamp.nuget_* $(RMFILE) stamp.nuget_*
clean_msbuild: clean_dotnet:
$(MSBUILD) src/KeePass.sln -target:clean $(MSBUILD_PARAM) $(DOTNET) clean src/KeePass.sln $(DOTNET_PARAM)
clean: clean_native clean_java clean_nuget clean_msbuild clean: clean_native clean_java clean_nuget clean_dotnet
distclean: clean distclean: clean
ifneq ("$(wildcard ./allow_git_clean)","") ifneq ("$(wildcard ./allow_git_clean)","")

View File

@@ -11,10 +11,10 @@ Regular stable releases of Keepass2Android are available on [Google Play](https:
Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet). Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet).
# How can I contribute? # How can I contribute?
* Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](http://crowdin.net/project/keepass2android) * Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](https://crowdin.net/project/keepass2android)
* Add features by [creating a plugin](How-to-create-a-plug-in_.md) or creating a pull request. You might want to contact me before you start working so I can coordinate efforts. * Add features by [creating a plugin](How-to-create-a-plug-in_.md) or creating a pull request. You might want to contact me before you start working so I can coordinate efforts.
* [Become a GitHub sponsor to boost 🚀 development](https://github.com/sponsors/PhilippC) * [Become a GitHub sponsor to boost 🚀 development](https://github.com/sponsors/PhilippC)
* [Make a donation](http://philipp.crocoll.net/donate.php) * [Make a donation](https://philipp.crocoll.net/donate.php)
# How do I learn more? # How do I learn more?
Please see the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation) for further information. Please see the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation) for further information.

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -40,8 +40,8 @@ namespace KeePassLib.Cryptography
Null = 0, Null = 0,
/// <summary> /// <summary>
/// A variant of the ARCFour algorithm (RC4 incompatible). /// A variant of the ArcFour algorithm (RC4 incompatible).
/// </summary> /// Insecure; for backward compatibility only.
/// </summary> /// </summary>
ArcFourVariant = 1, ArcFourVariant = 1,
@@ -66,56 +66,60 @@ namespace KeePassLib.Cryptography
/// </summary> /// </summary>
public sealed class CryptoRandomStream : IDisposable public sealed class CryptoRandomStream : IDisposable
{ {
private readonly CrsAlgorithm m_crsAlgorithm; private readonly CrsAlgorithm m_alg;
private bool m_bDisposed = false;
private byte[] m_pbState = null; private readonly byte[] m_pbKey = null;
private readonly byte[] m_pbIV = null;
private readonly ChaCha20Cipher m_chacha20 = null;
private readonly Salsa20Cipher m_salsa20 = null;
private readonly byte[] m_pbState = null;
private byte m_i = 0; private byte m_i = 0;
private byte m_j = 0; private byte m_j = 0;
private Salsa20Cipher m_salsa20 = null;
private ChaCha20Cipher m_chacha20 = null;
/// <summary> /// <summary>
/// Construct a new cryptographically secure random stream object. /// Construct a new cryptographically secure random stream object.
/// </summary> /// </summary>
/// <param name="genAlgorithm">Algorithm to use.</param> /// <param name="a">Algorithm to use.</param>
/// <param name="pbKey">Initialization key. Must not be <c>null</c> and /// <param name="pbKey">Initialization key. Must not be <c>null</c>
/// must contain at least 1 byte.</param> /// and must contain at least 1 byte.</param>
public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey)
{ {
if (pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } if (pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); }
/// <exception cref="System.ArgumentNullException">Thrown if the
int cbKey = pbKey.Length; int cbKey = pbKey.Length;
if (cbKey <= 0) if (cbKey <= 0)
{ {
Debug.Assert(false); // Need at least one byte Debug.Assert(false); // Need at least one byte
throw new ArgumentOutOfRangeException("pbKey"); throw new ArgumentOutOfRangeException("pbKey");
} }
/// <paramref name="pbKey" /> parameter is <c>null</c>.</exception>
m_crsAlgorithm = a; m_alg = a;
/// <exception cref="System.ArgumentException">Thrown if the
if (a == CrsAlgorithm.ChaCha20) if (a == CrsAlgorithm.ChaCha20)
{ {
byte[] pbKey32 = new byte[32]; m_pbKey = new byte[32];
byte[] pbIV12 = new byte[12]; m_pbIV = new byte[12];
/// <paramref name="pbKey" /> parameter contains no bytes or the
using (SHA512Managed h = new SHA512Managed()) using (SHA512Managed h = new SHA512Managed())
{ {
byte[] pbHash = h.ComputeHash(pbKey); byte[] pbHash = h.ComputeHash(pbKey);
Array.Copy(pbHash, pbKey32, 32); Array.Copy(pbHash, m_pbKey, 32);
Array.Copy(pbHash, 32, pbIV12, 0, 12); Array.Copy(pbHash, 32, m_pbIV, 0, 12);
MemUtil.ZeroByteArray(pbHash); MemUtil.ZeroByteArray(pbHash);
} }
/// algorithm is unknown.</exception>
m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true); m_chacha20 = new ChaCha20Cipher(m_pbKey, m_pbIV, true);
} }
else if (a == CrsAlgorithm.Salsa20) else if (a == CrsAlgorithm.Salsa20)
{ {
byte[] pbKey32 = CryptoUtil.HashSha256(pbKey); m_pbKey = CryptoUtil.HashSha256(pbKey);
byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, m_pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
0x97, 0x20, 0x5D, 0x2A }; // Unique constant 0x97, 0x20, 0x5D, 0x2A }; // Unique constant
m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8); m_salsa20 = new Salsa20Cipher(m_pbKey, m_pbIV);
} }
else if (a == CrsAlgorithm.ArcFourVariant) else if (a == CrsAlgorithm.ArcFourVariant)
{ {
@@ -159,17 +163,22 @@ namespace KeePassLib.Cryptography
{ {
if (disposing) if (disposing)
{ {
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) if (m_alg == CrsAlgorithm.ChaCha20)
m_chacha20.Dispose(); m_chacha20.Dispose();
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) else if (m_alg == CrsAlgorithm.Salsa20)
m_salsa20.Dispose(); m_salsa20.Dispose();
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) else if (m_alg == CrsAlgorithm.ArcFourVariant)
{ {
MemUtil.ZeroByteArray(m_pbState); MemUtil.ZeroByteArray(m_pbState);
m_i = 0; m_i = 0;
m_j = 0; m_j = 0;
} }
else { Debug.Assert(false); } else { Debug.Assert(false); }
if (m_pbKey != null) MemUtil.ZeroByteArray(m_pbKey);
if (m_pbIV != null) MemUtil.ZeroByteArray(m_pbIV);
m_bDisposed = true;
} }
} }
@@ -180,19 +189,20 @@ namespace KeePassLib.Cryptography
/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns> /// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns>
public byte[] GetRandomBytes(uint uRequestedCount) public byte[] GetRandomBytes(uint uRequestedCount)
{ {
if(uRequestedCount == 0) return MemUtil.EmptyByteArray; if (m_bDisposed) throw new ObjectDisposedException(null);
if (uRequestedCount == 0) return MemUtil.EmptyByteArray;
if (uRequestedCount > (uint)int.MaxValue) if (uRequestedCount > (uint)int.MaxValue)
throw new ArgumentOutOfRangeException("uRequestedCount"); throw new ArgumentOutOfRangeException("uRequestedCount");
int cb = (int)uRequestedCount; int cb = (int)uRequestedCount;
byte[] pbRet = new byte[cb]; byte[] pbRet = new byte[cb];
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) if (m_alg == CrsAlgorithm.ChaCha20)
m_chacha20.Encrypt(pbRet, 0, cb); m_chacha20.Encrypt(pbRet, 0, cb);
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) else if (m_alg == CrsAlgorithm.Salsa20)
m_salsa20.Encrypt(pbRet, 0, cb); m_salsa20.Encrypt(pbRet, 0, cb);
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) else if (m_alg == CrsAlgorithm.ArcFourVariant)
{ {
unchecked unchecked
{ {
@@ -221,6 +231,25 @@ namespace KeePassLib.Cryptography
return MemUtil.BytesToUInt64(pb); return MemUtil.BytesToUInt64(pb);
} }
internal ulong GetRandomUInt64(ulong uMaxExcl)
{
if (uMaxExcl == 0) { Debug.Assert(false); throw new ArgumentOutOfRangeException("uMaxExcl"); }
ulong uGen, uRem;
do
{
uGen = GetRandomUInt64();
uRem = uGen % uMaxExcl;
}
while ((uGen - uRem) > (ulong.MaxValue - (uMaxExcl - 1UL)));
// This ensures that the last number of the block (i.e.
// (uGen - uRem) + (uMaxExcl - 1)) is generatable;
// for signed longs, overflow to negative number:
// while((uGen - uRem) + (uMaxExcl - 1) < 0);
return uRem;
}
#if CRSBENCHMARK #if CRSBENCHMARK
public static string Benchmark() public static string Benchmark()
{ {
@@ -237,21 +266,20 @@ namespace KeePassLib.Cryptography
return str; return str;
} }
private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize) private static int BenchTime(CrsAlgorithm a, int nRounds, int cbData)
{ {
byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 }; byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 };
int nStart = Environment.TickCount; int tStart = Environment.TickCount;
for(int i = 0; i < nRounds; ++i) for(int i = 0; i < nRounds; ++i)
{ {
using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey)) using(CryptoRandomStream crs = new CryptoRandomStream(a, pbKey))
{ {
c.GetRandomBytes((uint)nDataSize); crs.GetRandomBytes((uint)cbData);
} }
} }
int nEnd = Environment.TickCount;
return (nEnd - nStart); return (Environment.TickCount - tStart);
} }
#endif #endif
} }

View File

@@ -1,65 +0,0 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator
{
internal static class CharSetBasedGenerator
{
internal static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crsRandomSource)
{
psOut = ProtectedString.Empty;
if(pwProfile.Length == 0) return PwgError.Success;
PwCharSet pcs = new PwCharSet(pwProfile.CharSet.ToString());
char[] vGenerated = new char[pwProfile.Length];
PwGenerator.PrepareCharSet(pcs, pwProfile);
for(int nIndex = 0; nIndex < (int)pwProfile.Length; ++nIndex)
{
char ch = PwGenerator.GenerateCharacter(pwProfile, pcs,
crsRandomSource);
if(ch == char.MinValue)
{
MemUtil.ZeroArray<char>(vGenerated);
return PwgError.TooFewCharacters;
}
vGenerated[nIndex] = ch;
}
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated);
psOut = new ProtectedString(true, pbUtf8);
MemUtil.ZeroByteArray(pbUtf8);
MemUtil.ZeroArray<char>(vGenerated);
return PwgError.Success;
}
}
}

View File

@@ -1,173 +0,0 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator
{
internal static class PatternBasedGenerator
{
internal static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crsRandomSource)
{
psOut = ProtectedString.Empty;
LinkedList<char> vGenerated = new LinkedList<char>();
PwCharSet pcsCurrent = new PwCharSet();
PwCharSet pcsCustom = new PwCharSet();
PwCharSet pcsUsed = new PwCharSet();
bool bInCharSetDef = false;
string strPattern = ExpandPattern(pwProfile.Pattern);
if(strPattern.Length == 0) return PwgError.Success;
CharStream csStream = new CharStream(strPattern);
char ch = csStream.ReadChar();
while(ch != char.MinValue)
{
pcsCurrent.Clear();
bool bGenerateChar = false;
if(ch == '\\')
{
ch = csStream.ReadChar();
if(ch == char.MinValue) // Backslash at the end
{
vGenerated.AddLast('\\');
break;
}
if(bInCharSetDef) pcsCustom.Add(ch);
else
{
vGenerated.AddLast(ch);
pcsUsed.Add(ch);
}
}
else if(ch == '[')
{
pcsCustom.Clear();
bInCharSetDef = true;
}
else if(ch == ']')
{
pcsCurrent.Add(pcsCustom.ToString());
bInCharSetDef = false;
bGenerateChar = true;
}
else if(bInCharSetDef)
{
if(pcsCustom.AddCharSet(ch) == false)
pcsCustom.Add(ch);
}
else if(pcsCurrent.AddCharSet(ch) == false)
{
vGenerated.AddLast(ch);
pcsUsed.Add(ch);
}
else bGenerateChar = true;
if(bGenerateChar)
{
PwGenerator.PrepareCharSet(pcsCurrent, pwProfile);
if(pwProfile.NoRepeatingCharacters)
pcsCurrent.Remove(pcsUsed.ToString());
char chGen = PwGenerator.GenerateCharacter(pwProfile,
pcsCurrent, crsRandomSource);
if(chGen == char.MinValue) return PwgError.TooFewCharacters;
vGenerated.AddLast(chGen);
pcsUsed.Add(chGen);
}
ch = csStream.ReadChar();
}
if(vGenerated.Count == 0) return PwgError.Success;
char[] vArray = new char[vGenerated.Count];
vGenerated.CopyTo(vArray, 0);
if(pwProfile.PatternPermutePassword)
PwGenerator.ShufflePassword(vArray, crsRandomSource);
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vArray);
psOut = new ProtectedString(true, pbUtf8);
MemUtil.ZeroByteArray(pbUtf8);
MemUtil.ZeroArray<char>(vArray);
vGenerated.Clear();
return PwgError.Success;
}
private static string ExpandPattern(string strPattern)
{
Debug.Assert(strPattern != null); if(strPattern == null) return string.Empty;
string str = strPattern;
while(true)
{
int nOpen = FindFirstUnescapedChar(str, '{');
int nClose = FindFirstUnescapedChar(str, '}');
if((nOpen >= 0) && (nOpen < nClose))
{
string strCount = str.Substring(nOpen + 1, nClose - nOpen - 1);
str = str.Remove(nOpen, nClose - nOpen + 1);
uint uRepeat;
if(StrUtil.TryParseUInt(strCount, out uRepeat) && (nOpen >= 1))
{
if(uRepeat == 0)
str = str.Remove(nOpen - 1, 1);
else
str = str.Insert(nOpen, new string(str[nOpen - 1], (int)uRepeat - 1));
}
}
else break;
}
return str;
}
private static int FindFirstUnescapedChar(string str, char ch)
{
for(int i = 0; i < str.Length; ++i)
{
char chCur = str[i];
if(chCur == '\\') ++i; // Next is escaped, skip it
else if(chCur == ch) return i;
}
return -1;
}
}
}

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -19,122 +19,81 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator namespace KeePassLib.Cryptography.PasswordGenerator
{ {
public sealed class PwCharSet public sealed class PwCharSet : IEquatable<PwCharSet>
{ {
public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static readonly string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string LowerCase = "abcdefghijklmnopqrstuvwxyz"; public static readonly string LowerCase = "abcdefghijklmnopqrstuvwxyz";
public const string Digits = "0123456789"; public static readonly string Digits = "0123456789";
public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ"; public static readonly string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ";
public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz"; public static readonly string LowerConsonants = "bcdfghjklmnpqrstvwxyz";
public const string UpperVowels = "AEIOU"; public static readonly string UpperVowels = "AEIOU";
public const string LowerVowels = "aeiou"; public static readonly string LowerVowels = "aeiou";
public const string Punctuation = @",.;:"; public static readonly string Punctuation = ",.;:";
public const string Brackets = @"[]{}()<>"; public static readonly string Brackets = @"[]{}()<>";
public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; public static readonly string Special = "!\"#$%&'*+,./:;=?@\\^`|~";
public static readonly string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
public const string UpperHex = "0123456789ABCDEF"; public static readonly string UpperHex = "0123456789ABCDEF";
public const string LowerHex = "0123456789abcdef"; public static readonly string LowerHex = "0123456789abcdef";
public const string Invalid = "\t\r\n"; public static readonly string LookAlike = "O0Il1|";
public const string LookAlike = @"O0l1I|";
internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
private const int CharTabSize = (0x10000 / 8);
private List<char> m_vChars = new List<char>();
private byte[] m_vTab = new byte[CharTabSize];
private static string m_strHighAnsi = null;
public static string HighAnsiChars
{
get
{
if(m_strHighAnsi == null) { new PwCharSet(); } // Create string
Debug.Assert(m_strHighAnsi != null);
return m_strHighAnsi;
}
}
private static string m_strSpecial = null;
public static string SpecialChars
{
get
{
if(m_strSpecial == null) { new PwCharSet(); } // Create string
Debug.Assert(m_strSpecial != null);
return m_strSpecial;
}
}
/// <summary> /// <summary>
/// Create a new, empty character set collection object. /// Latin-1 Supplement except U+00A0 (NBSP) and U+00AD (SHY).
/// </summary>
public static readonly string Latin1S =
"\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7" +
"\u00A8\u00A9\u00AA\u00AB\u00AC\u00AE\u00AF" +
"\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7" +
"\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF" +
"\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7" +
"\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF" +
"\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7" +
"\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF" +
"\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7" +
"\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF" +
"\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7" +
"\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF";
// internal static readonly string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
[Obsolete]
public static string SpecialChars { get { return PwCharSet.Special; } }
[Obsolete]
public static string HighAnsiChars { get { return PwCharSet.Latin1S; } }
private readonly List<char> m_lChars = new List<char>();
private readonly byte[] m_vTab = new byte[0x10000 / 8];
/// <summary>
/// Create a new, empty character set.
/// </summary> /// </summary>
public PwCharSet() public PwCharSet()
{ {
Initialize(true); Debug.Assert(PwCharSet.Latin1S.Length == (16 * 6 - 2));
} }
public PwCharSet(string strCharSet) public PwCharSet(string strCharSet)
{ {
Initialize(true);
Add(strCharSet); Add(strCharSet);
} }
private PwCharSet(bool bFullInitialize)
{
Initialize(bFullInitialize);
}
private void Initialize(bool bFullInitialize)
{
Clear();
if(!bFullInitialize) return;
if(m_strHighAnsi == null)
{
StringBuilder sbHighAnsi = new StringBuilder();
// [U+0080, U+009F] are C1 control characters,
// U+00A0 is non-breaking space
for(char ch = '\u00A1'; ch <= '\u00AC'; ++ch)
sbHighAnsi.Append(ch);
// U+00AD is soft hyphen (format character)
for(char ch = '\u00AE'; ch < '\u00FF'; ++ch)
sbHighAnsi.Append(ch);
sbHighAnsi.Append('\u00FF');
m_strHighAnsi = sbHighAnsi.ToString();
}
if(m_strSpecial == null)
{
PwCharSet pcs = new PwCharSet(false);
pcs.AddRange('!', '/');
pcs.AddRange(':', '@');
pcs.AddRange('[', '`');
pcs.Add(@"|~");
pcs.Remove(@"-_ ");
pcs.Remove(PwCharSet.Brackets);
m_strSpecial = pcs.ToString();
}
}
/// <summary> /// <summary>
/// Number of characters in this set. /// Number of characters in this set.
/// </summary> /// </summary>
public uint Size public uint Size
{ {
get { return (uint)m_vChars.Count; } get { return (uint)m_lChars.Count; }
} }
/// <summary> /// <summary>
@@ -147,19 +106,39 @@ namespace KeePassLib.Cryptography.PasswordGenerator
{ {
get get
{ {
if(uPos >= (uint)m_vChars.Count) if (uPos >= (uint)m_lChars.Count)
throw new ArgumentOutOfRangeException("uPos"); throw new ArgumentOutOfRangeException("uPos");
return m_vChars[(int)uPos]; return m_lChars[(int)uPos];
} }
} }
public bool Equals(PwCharSet other)
{
if (object.ReferenceEquals(other, this)) return true;
if (object.ReferenceEquals(other, null)) return false;
if (m_lChars.Count != other.m_lChars.Count) return false;
return MemUtil.ArraysEqual(m_vTab, other.m_vTab);
}
public override bool Equals(object obj)
{
return Equals(obj as PwCharSet);
}
public override int GetHashCode()
{
return (int)MemUtil.Hash32(m_vTab, 0, m_vTab.Length);
}
/// <summary> /// <summary>
/// Remove all characters from this set. /// Remove all characters from this set.
/// </summary> /// </summary>
public void Clear() public void Clear()
{ {
m_vChars.Clear(); m_lChars.Clear();
Array.Clear(m_vTab, 0, m_vTab.Length); Array.Clear(m_vTab, 0, m_vTab.Length);
} }
@@ -191,7 +170,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator
if (!Contains(ch)) if (!Contains(ch))
{ {
m_vChars.Add(ch); m_lChars.Add(ch);
m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); m_vTab[ch / 8] |= (byte)(1 << (ch % 8));
} }
} }
@@ -205,8 +184,6 @@ namespace KeePassLib.Cryptography.PasswordGenerator
Debug.Assert(strCharSet != null); Debug.Assert(strCharSet != null);
if (strCharSet == null) throw new ArgumentNullException("strCharSet"); if (strCharSet == null) throw new ArgumentNullException("strCharSet");
m_vChars.Capacity = m_vChars.Count + strCharSet.Length;
foreach (char ch in strCharSet) foreach (char ch in strCharSet)
Add(ch); Add(ch);
} }
@@ -226,8 +203,6 @@ namespace KeePassLib.Cryptography.PasswordGenerator
public void AddRange(char chMin, char chMax) public void AddRange(char chMin, char chMax)
{ {
m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1;
for (char ch = chMin; ch < chMax; ++ch) for (char ch = chMin; ch < chMax; ++ch)
Add(ch); Add(ch);
@@ -241,11 +216,13 @@ namespace KeePassLib.Cryptography.PasswordGenerator
switch (chCharSetIdentifier) switch (chCharSetIdentifier)
{ {
case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break; case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break;
case 'A': Add(PwCharSet.LowerCase, PwCharSet.UpperCase, case 'A':
Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
PwCharSet.Digits); break; PwCharSet.Digits); break;
case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break; case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break;
case 'c': Add(PwCharSet.LowerConsonants); break; case 'c': Add(PwCharSet.LowerConsonants); break;
case 'C': Add(PwCharSet.LowerConsonants, case 'C':
Add(PwCharSet.LowerConsonants,
PwCharSet.UpperConsonants); break; PwCharSet.UpperConsonants); break;
case 'z': Add(PwCharSet.UpperConsonants); break; case 'z': Add(PwCharSet.UpperConsonants); break;
case 'd': Add(PwCharSet.Digits); break; // Digit case 'd': Add(PwCharSet.Digits); break; // Digit
@@ -257,12 +234,13 @@ namespace KeePassLib.Cryptography.PasswordGenerator
case 'p': Add(PwCharSet.Punctuation); break; case 'p': Add(PwCharSet.Punctuation); break;
case 'b': Add(PwCharSet.Brackets); break; case 'b': Add(PwCharSet.Brackets); break;
case 's': Add(PwCharSet.PrintableAsciiSpecial); break; case 's': Add(PwCharSet.PrintableAsciiSpecial); break;
case 'S': Add(PwCharSet.UpperCase, PwCharSet.LowerCase); case 'S':
Add(PwCharSet.UpperCase, PwCharSet.LowerCase);
Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break; Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break;
case 'v': Add(PwCharSet.LowerVowels); break; case 'v': Add(PwCharSet.LowerVowels); break;
case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break; case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break;
case 'Z': Add(PwCharSet.UpperVowels); break; case 'Z': Add(PwCharSet.UpperVowels); break;
case 'x': Add(m_strHighAnsi); break; case 'x': Add(PwCharSet.Latin1S); break;
default: bResult = false; break; default: bResult = false; break;
} }
@@ -272,7 +250,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator
public bool Remove(char ch) public bool Remove(char ch)
{ {
m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8))); m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8)));
return m_vChars.Remove(ch); return m_lChars.Remove(ch);
} }
public bool Remove(string strCharacters) public bool Remove(string strCharacters)
@@ -306,8 +284,8 @@ namespace KeePassLib.Cryptography.PasswordGenerator
/// <returns>String containing all character set characters.</returns> /// <returns>String containing all character set characters.</returns>
public override string ToString() public override string ToString()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder(m_lChars.Count);
foreach(char ch in m_vChars) foreach (char ch in m_lChars)
sb.Append(ch); sb.Append(ch);
return sb.ToString(); return sb.ToString();
@@ -320,13 +298,13 @@ namespace KeePassLib.Cryptography.PasswordGenerator
sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_');
sb.Append(RemoveIfAllExist(m_strSpecial) ? 'S' : '_'); sb.Append(RemoveIfAllExist(PwCharSet.Special) ? 'S' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_'); sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_');
sb.Append(RemoveIfAllExist(@"-") ? 'm' : '_'); sb.Append(RemoveIfAllExist("-") ? 'm' : '_');
sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_'); sb.Append(RemoveIfAllExist("_") ? 'u' : '_');
sb.Append(RemoveIfAllExist(@" ") ? 's' : '_'); sb.Append(RemoveIfAllExist(" ") ? 's' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_'); sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_');
sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_'); sb.Append(RemoveIfAllExist(PwCharSet.Latin1S) ? 'H' : '_');
return sb.ToString(); return sb.ToString();
} }
@@ -339,13 +317,13 @@ namespace KeePassLib.Cryptography.PasswordGenerator
if (strRanges[0] != '_') Add(PwCharSet.UpperCase); if (strRanges[0] != '_') Add(PwCharSet.UpperCase);
if (strRanges[1] != '_') Add(PwCharSet.LowerCase); if (strRanges[1] != '_') Add(PwCharSet.LowerCase);
if (strRanges[2] != '_') Add(PwCharSet.Digits); if (strRanges[2] != '_') Add(PwCharSet.Digits);
if(strRanges[3] != '_') Add(m_strSpecial); if (strRanges[3] != '_') Add(PwCharSet.Special);
if (strRanges[4] != '_') Add(PwCharSet.Punctuation); if (strRanges[4] != '_') Add(PwCharSet.Punctuation);
if (strRanges[5] != '_') Add('-'); if (strRanges[5] != '_') Add('-');
if (strRanges[6] != '_') Add('_'); if (strRanges[6] != '_') Add('_');
if (strRanges[7] != '_') Add(' '); if (strRanges[7] != '_') Add(' ');
if (strRanges[8] != '_') Add(PwCharSet.Brackets); if (strRanges[8] != '_') Add(PwCharSet.Brackets);
if(strRanges[9] != '_') Add(m_strHighAnsi); if (strRanges[9] != '_') Add(PwCharSet.Latin1S);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -20,9 +20,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Security.Cryptography;
using System.Text; using System.Text;
#if !KeePassUAP
using System.Security.Cryptography;
#endif
using KeePassLib.Resources;
using KeePassLib.Security; using KeePassLib.Security;
using KeePassLib.Utility; using KeePassLib.Utility;
@@ -33,95 +37,78 @@ namespace KeePassLib.Cryptography.PasswordGenerator
Success = 0, Success = 0,
Unknown = 1, Unknown = 1,
TooFewCharacters = 2, TooFewCharacters = 2,
UnknownAlgorithm = 3 UnknownAlgorithm = 3,
InvalidCharSet = 4,
InvalidPattern = 5
} }
/// <summary> /// <summary>
/// Utility functions for generating random passwords. /// Password generator.
/// </summary> /// </summary>
public static class PwGenerator public static class PwGenerator
{ {
public static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, byte[] pbUserEntropy, private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy,
CustomPwGeneratorPool pwAlgorithmPool) out byte[] pbKey)
{ {
Debug.Assert(pwProfile != null); pbKey = CryptoRandom.Instance.GetRandomBytes(128);
if (pwProfile == null) throw new ArgumentNullException("pwProfile");
CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy);
PwgError e = PwgError.Unknown;
if (pwProfile.GeneratorType == PasswordGeneratorType.CharSet)
e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs);
else if (pwProfile.GeneratorType == PasswordGeneratorType.Pattern)
e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs);
else if (pwProfile.GeneratorType == PasswordGeneratorType.Custom)
e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool);
else { Debug.Assert(false); psOut = ProtectedString.Empty; }
return e;
}
private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy)
{
byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128);
// Mix in additional entropy // Mix in additional entropy
Debug.Assert(pbKey.Length >= 64); Debug.Assert(pbKey.Length >= 64);
if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length != 0))
{ {
using (SHA512Managed h = new SHA512Managed()) using (SHA512Managed h = new SHA512Managed())
{ {
byte[] pbHash = h.ComputeHash(pbAdditionalEntropy); byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length); MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
MemUtil.ZeroByteArray(pbHash);
} }
} }
return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey);
} }
internal static char GenerateCharacter(PwProfile pwProfile, internal static char GenerateCharacter(PwCharSet pwCharSet,
PwCharSet pwCharSet, CryptoRandomStream crsRandomSource) CryptoRandomStream crsRandomSource)
{ {
if (pwCharSet.Size == 0) return char.MinValue; uint cc = pwCharSet.Size;
if (cc == 0) return char.MinValue;
ulong uIndex = crsRandomSource.GetRandomUInt64(); uint i = (uint)crsRandomSource.GetRandomUInt64(cc);
uIndex %= (ulong)pwCharSet.Size; return pwCharSet[i];
char ch = pwCharSet[(uint)uIndex];
if (pwProfile.NoRepeatingCharacters)
pwCharSet.Remove(ch);
return ch;
} }
internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
{ {
pwCharSet.Remove(PwCharSet.Invalid); uint cc = pwCharSet.Size;
for (uint i = 0; i < cc; ++i)
{
char ch = pwCharSet[i];
if ((ch == char.MinValue) || (ch == '\t') || (ch == '\r') ||
(ch == '\n') || char.IsSurrogate(ch))
return false;
}
if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike);
if (pwProfile.ExcludeCharacters.Length > 0) if (!string.IsNullOrEmpty(pwProfile.ExcludeCharacters))
pwCharSet.Remove(pwProfile.ExcludeCharacters); pwCharSet.Remove(pwProfile.ExcludeCharacters);
return true;
} }
internal static void ShufflePassword(char[] pPassword, internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource)
CryptoRandomStream crsRandomSource)
{ {
Debug.Assert(pPassword != null); if (pPassword == null) return; if (v == null) { Debug.Assert(false); return; }
Debug.Assert(crsRandomSource != null); if (crsRandomSource == null) return; if (crsRandomSource == null) { Debug.Assert(false); return; }
if (pPassword.Length <= 1) return; // Nothing to shuffle for (int i = v.Length - 1; i >= 1; --i)
for (int nSelect = 0; nSelect < pPassword.Length; ++nSelect)
{ {
ulong uRandomIndex = crsRandomSource.GetRandomUInt64(); int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1));
uRandomIndex %= (ulong)(pPassword.Length - nSelect);
char chTemp = pPassword[nSelect]; char t = v[i];
pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex]; v[i] = v[j];
pPassword[nSelect + (int)uRandomIndex] = chTemp; v[j] = t;
} }
} }
@@ -135,7 +122,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator
if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm;
string strID = pwProfile.CustomAlgorithmUuid; string strID = pwProfile.CustomAlgorithmUuid;
if (string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } if (string.IsNullOrEmpty(strID)) return PwgError.UnknownAlgorithm;
byte[] pbUuid = Convert.FromBase64String(strID); byte[] pbUuid = Convert.FromBase64String(strID);
PwUuid uuid = new PwUuid(pbUuid); PwUuid uuid = new PwUuid(pbUuid);
@@ -148,5 +135,57 @@ namespace KeePassLib.Cryptography.PasswordGenerator
psOut = pwd; psOut = pwd;
return PwgError.Success; return PwgError.Success;
} }
internal static string ErrorToString(PwgError e, bool bHeader)
{
if (e == PwgError.Success) { Debug.Assert(false); return string.Empty; }
if ((e == PwgError.Unknown) && bHeader) return KLRes.PwGenFailed;
string str = KLRes.UnknownError;
switch (e)
{
// case PwgError.Success:
// break;
case PwgError.Unknown:
break;
case PwgError.TooFewCharacters:
str = KLRes.CharSetTooFewChars;
break;
case PwgError.UnknownAlgorithm:
str = KLRes.AlgorithmUnknown;
break;
case PwgError.InvalidCharSet:
str = KLRes.CharSetInvalid;
break;
case PwgError.InvalidPattern:
str = KLRes.PatternInvalid;
break;
default:
Debug.Assert(false);
break;
}
if (bHeader)
str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
return str;
}
internal static string ErrorToString(Exception ex, bool bHeader)
{
string str = ((ex == null) ? KLRes.UnknownError :
StrUtil.FormatException(ex));
if (bHeader)
str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
return str;
}
} }
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -19,8 +19,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using KeePassLib.Utility; using KeePassLib.Utility;
@@ -28,15 +28,17 @@ namespace KeePassLib.Cryptography
{ {
public static class PopularPasswords public static class PopularPasswords
{ {
private static Dictionary<int, Dictionary<string, bool>> m_dicts = private static readonly Dictionary<int, Dictionary<char[], bool>> g_dicts =
new Dictionary<int, Dictionary<string, bool>>(); new Dictionary<int, Dictionary<char[], bool>>();
internal static int MaxLength internal static int MaxLength
{ {
get get
{ {
Debug.Assert(g_dicts.Count > 0); // Should be initialized
int iMaxLen = 0; int iMaxLen = 0;
foreach(int iLen in m_dicts.Keys) foreach (int iLen in g_dicts.Keys)
{ {
if (iLen > iMaxLen) iMaxLen = iLen; if (iLen > iMaxLen) iMaxLen = iLen;
} }
@@ -47,8 +49,8 @@ namespace KeePassLib.Cryptography
internal static bool ContainsLength(int nLength) internal static bool ContainsLength(int nLength)
{ {
Dictionary<string, bool> dDummy; Dictionary<char[], bool> dDummy;
return m_dicts.TryGetValue(nLength, out dDummy); return g_dicts.TryGetValue(nLength, out dDummy);
} }
public static bool IsPopularPassword(char[] vPassword) public static bool IsPopularPassword(char[] vPassword)
@@ -62,28 +64,30 @@ namespace KeePassLib.Cryptography
if (vPassword == null) throw new ArgumentNullException("vPassword"); if (vPassword == null) throw new ArgumentNullException("vPassword");
if (vPassword.Length == 0) { uDictSize = 0; return false; } if (vPassword.Length == 0) { uDictSize = 0; return false; }
string str = new string(vPassword); #if DEBUG
Array.ForEach(vPassword, ch => Debug.Assert(ch == char.ToLower(ch)));
#endif
try { return IsPopularPasswordPriv(str, out uDictSize); } try { return IsPopularPasswordPriv(vPassword, out uDictSize); }
catch (Exception) { Debug.Assert(false); } catch (Exception) { Debug.Assert(false); }
uDictSize = 0; uDictSize = 0;
return false; return false;
} }
private static bool IsPopularPasswordPriv(string str, out ulong uDictSize) private static bool IsPopularPasswordPriv(char[] vPassword, out ulong uDictSize)
{ {
Debug.Assert(m_dicts.Count > 0); // Should be initialized with data Debug.Assert(g_dicts.Count > 0); // Should be initialized with data
Dictionary<string, bool> d; Dictionary<char[], bool> d;
if(!m_dicts.TryGetValue(str.Length, out d)) if (!g_dicts.TryGetValue(vPassword.Length, out d))
{ {
uDictSize = 0; uDictSize = 0;
return false; return false;
} }
uDictSize = (ulong)d.Count; uDictSize = (ulong)d.Count;
return d.ContainsKey(str); return d.ContainsKey(vPassword);
} }
public static void Add(byte[] pbData, bool bGZipped) public static void Add(byte[] pbData, bool bGZipped)
@@ -96,30 +100,27 @@ namespace KeePassLib.Cryptography
string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length);
if (string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } if (string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; }
if(!char.IsWhiteSpace(strData[strData.Length - 1]))
strData += "\n";
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for(int i = 0; i < strData.Length; ++i) for (int i = 0; i <= strData.Length; ++i)
{ {
char ch = strData[i]; char ch = ((i == strData.Length) ? ' ' : strData[i]);
if (char.IsWhiteSpace(ch)) if (char.IsWhiteSpace(ch))
{ {
int cc = sb.Length; int cc = sb.Length;
if (cc > 0) if (cc > 0)
{ {
string strWord = sb.ToString(); char[] vWord = new char[cc];
Debug.Assert(strWord.Length == cc); sb.CopyTo(0, vWord, 0, cc);
Dictionary<string, bool> d; Dictionary<char[], bool> d;
if(!m_dicts.TryGetValue(cc, out d)) if (!g_dicts.TryGetValue(cc, out d))
{ {
d = new Dictionary<string, bool>(); d = new Dictionary<char[], bool>(MemUtil.ArrayHelperExOfChar);
m_dicts[cc] = d; g_dicts[cc] = d;
} }
d[strWord] = true; d[vWord] = true;
sb.Remove(0, cc); sb.Remove(0, cc);
} }
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -19,8 +19,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using KeePassLib.Cryptography.PasswordGenerator; using KeePassLib.Cryptography.PasswordGenerator;
using KeePassLib.Utility; using KeePassLib.Utility;
@@ -35,19 +35,19 @@ namespace KeePassLib.Cryptography
{ {
private static class PatternID private static class PatternID
{ {
public const char LowerAlpha = 'L'; internal const char LowerAlpha = 'L';
public const char UpperAlpha = 'U'; internal const char UpperAlpha = 'U';
public const char Digit = 'D'; internal const char Digit = 'D';
public const char Special = 'S'; internal const char Special = 'S';
public const char High = 'H'; internal const char Latin1S = 'H';
public const char Other = 'X'; internal const char Other = 'X';
public const char Dictionary = 'W'; internal const char Dictionary = 'W';
public const char Repetition = 'R'; internal const char Repetition = 'R';
public const char Number = 'N'; internal const char Number = 'N';
public const char DiffSeq = 'C'; internal const char DiffSeq = 'C';
public const string All = "LUDSHXWRNC"; internal const string All = "LUDSHXWRNC";
} }
// private static class CharDistrib // private static class CharDistrib
@@ -125,7 +125,7 @@ namespace KeePassLib.Cryptography
private sealed class EntropyEncoder private sealed class EntropyEncoder
{ {
private readonly string m_strAlph; private readonly string m_strAlph;
private Dictionary<char, ulong> m_dHisto = new Dictionary<char, ulong>(); private readonly Dictionary<char, ulong> m_dHisto = new Dictionary<char, ulong>();
private readonly ulong m_uBaseWeight; private readonly ulong m_uBaseWeight;
private readonly ulong m_uCharWeight; private readonly ulong m_uCharWeight;
private readonly ulong m_uOccExclThreshold; private readonly ulong m_uOccExclThreshold;
@@ -189,7 +189,7 @@ namespace KeePassLib.Cryptography
private sealed class MultiEntropyEncoder private sealed class MultiEntropyEncoder
{ {
private Dictionary<char, EntropyEncoder> m_dEncs = private readonly Dictionary<char, EntropyEncoder> m_dEncs =
new Dictionary<char, EntropyEncoder>(); new Dictionary<char, EntropyEncoder>();
public MultiEntropyEncoder() public MultiEntropyEncoder()
@@ -281,7 +281,7 @@ namespace KeePassLib.Cryptography
} }
} }
private static object m_objSyncInit = new object(); private static readonly object m_objSyncInit = new object();
private static List<QeCharType> m_lCharTypes = null; private static List<QeCharType> m_lCharTypes = null;
private static void EnsureInitialized() private static void EnsureInitialized()
@@ -292,25 +292,20 @@ namespace KeePassLib.Cryptography
{ {
string strSpecial = PwCharSet.PrintableAsciiSpecial; string strSpecial = PwCharSet.PrintableAsciiSpecial;
if (strSpecial.IndexOf(' ') >= 0) { Debug.Assert(false); } if (strSpecial.IndexOf(' ') >= 0) { Debug.Assert(false); }
else strSpecial = strSpecial + " "; else strSpecial += " ";
int nSp = strSpecial.Length; int nSp = strSpecial.Length;
int nHi = PwCharSet.HighAnsiChars.Length; int nL1S = PwCharSet.Latin1S.Length;
m_lCharTypes = new List<QeCharType>(); m_lCharTypes = new List<QeCharType>()
{
m_lCharTypes.Add(new QeCharType(PatternID.LowerAlpha, new QeCharType(PatternID.LowerAlpha, PwCharSet.LowerCase, true),
PwCharSet.LowerCase, true)); new QeCharType(PatternID.UpperAlpha, PwCharSet.UpperCase, true),
m_lCharTypes.Add(new QeCharType(PatternID.UpperAlpha, new QeCharType(PatternID.Digit, PwCharSet.Digits, true),
PwCharSet.UpperCase, true)); new QeCharType(PatternID.Special, strSpecial, false),
m_lCharTypes.Add(new QeCharType(PatternID.Digit, new QeCharType(PatternID.Latin1S, PwCharSet.Latin1S, false),
PwCharSet.Digits, true)); new QeCharType(PatternID.Other, 0x10000 - (2 * 26) - 10 - nSp - nL1S)
m_lCharTypes.Add(new QeCharType(PatternID.Special, };
strSpecial, false));
m_lCharTypes.Add(new QeCharType(PatternID.High,
PwCharSet.HighAnsiChars, false));
m_lCharTypes.Add(new QeCharType(PatternID.Other,
0x10000 - (2 * 26) - 10 - nSp - nHi));
} }
} }
} }
@@ -318,30 +313,30 @@ namespace KeePassLib.Cryptography
/// <summary> /// <summary>
/// Estimate the quality of a password. /// Estimate the quality of a password.
/// </summary> /// </summary>
/// <param name="vPasswordChars">Password to check.</param> /// <param name="vPassword">Password to check.</param>
/// <returns>Estimated bit-strength of the password.</returns> /// <returns>Estimated bit-strength of the password.</returns>
public static uint EstimatePasswordBits(char[] vPasswordChars) public static uint EstimatePasswordBits(char[] vPassword)
{ {
if(vPasswordChars == null) { Debug.Assert(false); return 0; } if (vPassword == null) { Debug.Assert(false); return 0; }
if(vPasswordChars.Length == 0) return 0; if (vPassword.Length == 0) return 0;
EnsureInitialized(); EnsureInitialized();
int n = vPasswordChars.Length; int n = vPassword.Length;
List<QePatternInstance>[] vPatterns = new List<QePatternInstance>[n]; List<QePatternInstance>[] vPatterns = new List<QePatternInstance>[n];
for (int i = 0; i < n; ++i) for (int i = 0; i < n; ++i)
{ {
vPatterns[i] = new List<QePatternInstance>(); vPatterns[i] = new List<QePatternInstance>();
QePatternInstance piChar = new QePatternInstance(i, 1, QePatternInstance piChar = new QePatternInstance(i, 1,
GetCharType(vPasswordChars[i])); GetCharType(vPassword[i]));
vPatterns[i].Add(piChar); vPatterns[i].Add(piChar);
} }
FindRepetitions(vPasswordChars, vPatterns); FindRepetitions(vPassword, vPatterns);
FindNumbers(vPasswordChars, vPatterns); FindNumbers(vPassword, vPatterns);
FindDiffSeqs(vPasswordChars, vPatterns); FindDiffSeqs(vPassword, vPatterns);
FindPopularPasswords(vPasswordChars, vPatterns); FindPopularPasswords(vPassword, vPatterns);
// Encoders must not be static, because the entropy estimation // Encoders must not be static, because the entropy estimation
// may run concurrently in multiple threads and the encoders are // may run concurrently in multiple threads and the encoders are
@@ -382,7 +377,7 @@ namespace KeePassLib.Cryptography
{ {
Debug.Assert(s.Position == n); Debug.Assert(s.Position == n);
double dblCost = ComputePathCost(s.Path, vPasswordChars, double dblCost = ComputePathCost(s.Path, vPassword,
ecPattern, mcData); ecPattern, mcData);
if (dblCost < dblMinCost) dblMinCost = dblCost; if (dblCost < dblMinCost) dblMinCost = dblCost;
} }
@@ -420,11 +415,12 @@ namespace KeePassLib.Cryptography
{ {
if (pbUnprotectedUtf8 == null) { Debug.Assert(false); return 0; } if (pbUnprotectedUtf8 == null) { Debug.Assert(false); return 0; }
char[] vChars = StrUtil.Utf8.GetChars(pbUnprotectedUtf8); char[] v = StrUtil.Utf8.GetChars(pbUnprotectedUtf8);
uint uResult = EstimatePasswordBits(vChars); uint r;
MemUtil.ZeroArray<char>(vChars); try { r = EstimatePasswordBits(v); }
finally { MemUtil.ZeroArray<char>(v); }
return uResult; return r;
} }
private static QeCharType GetCharType(char ch) private static QeCharType GetCharType(char ch)
@@ -482,7 +478,7 @@ namespace KeePassLib.Cryptography
vLeet[i] = char.ToLower(DecodeLeetChar(ch)); vLeet[i] = char.ToLower(DecodeLeetChar(ch));
} }
char chErased = default(char); char chErased = default(char); // The value that Array.Clear uses
Debug.Assert(chErased == char.MinValue); Debug.Assert(chErased == char.MinValue);
int nMaxLen = Math.Min(n, PopularPasswords.MaxLength); int nMaxLen = Math.Min(n, PopularPasswords.MaxLength);
@@ -515,7 +511,12 @@ namespace KeePassLib.Cryptography
Debug.Assert(vLower[i] == chErased); Debug.Assert(vLower[i] == chErased);
} }
} }
MemUtil.ZeroArray<char>(vSub);
} }
MemUtil.ZeroArray<char>(vLower);
MemUtil.ZeroArray<char>(vLeet);
} }
private static bool EvalAddPopularPasswordPattern(List<QePatternInstance>[] vPatterns, private static bool EvalAddPopularPasswordPattern(List<QePatternInstance>[] vPatterns,
@@ -659,6 +660,8 @@ namespace KeePassLib.Cryptography
if (bFoundRep) ErasePart(v, x1, m, ref chErased); if (bFoundRep) ErasePart(v, x1, m, ref chErased);
} }
} }
MemUtil.ZeroArray<char>(v);
} }
private static bool PartsEqual(char[] v, int x1, int x2, int nLength) private static bool PartsEqual(char[] v, int x1, int x2, int nLength)
@@ -685,23 +688,25 @@ namespace KeePassLib.Cryptography
{ {
int n = vPassword.Length; int n = vPassword.Length;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; ++i) for (int i = 0; i < n; ++i)
{ {
char ch = vPassword[i]; char ch = vPassword[i];
if ((ch >= '0') && (ch <= '9')) sb.Append(ch); if ((ch >= '0') && (ch <= '9')) sb.Append(ch);
else else
{ {
AddNumberPattern(vPatterns, sb.ToString(), i - sb.Length); AddNumberPattern(vPatterns, sb, i - sb.Length);
sb.Remove(0, sb.Length); sb.Remove(0, sb.Length);
} }
} }
AddNumberPattern(vPatterns, sb.ToString(), n - sb.Length); AddNumberPattern(vPatterns, sb, n - sb.Length);
} }
private static void AddNumberPattern(List<QePatternInstance>[] vPatterns, private static void AddNumberPattern(List<QePatternInstance>[] vPatterns,
string strNumber, int i) StringBuilder sb, int i)
{ {
if(strNumber.Length <= 2) return; if (sb.Length <= 2) return;
string strNumber = sb.ToString();
int nZeros = 0; int nZeros = 0;
for (int j = 0; j < strNumber.Length; ++j) for (int j = 0; j < strNumber.Length; ++j)
@@ -733,17 +738,18 @@ namespace KeePassLib.Cryptography
private static void FindDiffSeqs(char[] vPassword, private static void FindDiffSeqs(char[] vPassword,
List<QePatternInstance>[] vPatterns) List<QePatternInstance>[] vPatterns)
{ {
int d = int.MinValue, p = 0; int n = vPassword.Length;
string str = new string(vPassword) + new string(char.MaxValue, 1); int d = int.MaxValue, p = 0;
for(int i = 1; i < str.Length; ++i) for (int i = 1; i <= n; ++i)
{ {
int dCur = (int)str[i] - (int)str[i - 1]; int dCur = ((i == n) ? int.MinValue :
((int)vPassword[i] - (int)vPassword[i - 1]));
if (dCur != d) if (dCur != d)
{ {
if ((i - p) >= 3) // At least 3 chars involved if ((i - p) >= 3) // At least 3 chars involved
{ {
QeCharType ct = GetCharType(str[p]); QeCharType ct = GetCharType(vPassword[p]);
double dblCost = ct.CharSize + Log2(i - p - 1); double dblCost = ct.CharSize + Log2(i - p - 1);
vPatterns[p].Add(new QePatternInstance(p, vPatterns[p].Add(new QePatternInstance(p,

View File

@@ -46,4 +46,12 @@ namespace KeePassLib.Delegates
public delegate void VoidDelegate(); public delegate void VoidDelegate();
public delegate string StrPwEntryDelegate(string str, PwEntry pe); public delegate string StrPwEntryDelegate(string str, PwEntry pe);
public delegate TResult GFunc<TResult>();
public delegate TResult GFunc<T, TResult>(T o);
public delegate TResult GFunc<T1, T2, TResult>(T1 o1, T2 o2);
public delegate TResult GFunc<T1, T2, T3, TResult>(T1 o1, T2 o2, T3 o3);
public delegate TResult GFunc<T1, T2, T3, T4, TResult>(T1 o1, T2 o2, T3 o3, T4 o4);
public delegate TResult GFunc<T1, T2, T3, T4, T5, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5);
public delegate TResult GFunc<T1, T2, T3, T4, T5, T6, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5, T6 o6);
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
#if KeePassLibSD #if KeePassLibSD
@@ -30,81 +31,26 @@ using KeePassLibSD;
using System.IO.Compression; using System.IO.Compression;
#endif #endif
using KeePassLib.Delegates;
namespace KeePassLib.Utility namespace KeePassLib.Utility
{ {
/// <summary> /// <summary>
/// Contains static buffer manipulation and string conversion routines. /// Buffer manipulation and conversion routines.
/// </summary> /// </summary>
public static class MemUtil public static class MemUtil
{ {
public static readonly byte[] EmptyByteArray = new byte[0]; public static readonly byte[] EmptyByteArray = new byte[0];
private static readonly uint[] m_vSBox = new uint[256] { internal static readonly ArrayHelperEx<char> ArrayHelperExOfChar =
0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230, new ArrayHelperEx<char>();
0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4,
0x07CE9E5B, 0x31788A0C, 0xF683F6F4, 0xEA061F49, private const MethodImplOptions MioNoOptimize =
0xFA5C2ACA, 0x4B9E494E, 0xB0AB25BA, 0x767731FC, #if KeePassLibSD
0x261893A7, 0x2B09F2CE, 0x046261E4, 0x41367B4B, MethodImplOptions.NoInlining;
0x18A7F225, 0x8F923C0E, 0x5EF3A325, 0x28D0435E, #else
0x84C22919, 0xED66873C, 0x8CEDE444, 0x7FC47C24, (MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining);
0xFCFC6BA3, 0x676F928D, 0xB4147187, 0xD8FB126E, #endif
0x7D798D17, 0xFF82E424, 0x1712FA5B, 0xABB09DD5,
0x8156BA63, 0x84E4D969, 0xC937FB9A, 0x2F1E5BFC,
0x178ECA11, 0x0E71CD5F, 0x52AAC6F4, 0x71EEFC8F,
0x7090D749, 0x21CACA31, 0x92996378, 0x0939A8A8,
0xE9EE1934, 0xD2718616, 0xF2500543, 0xB911873C,
0xD3CB3EEC, 0x2BA0DBEB, 0xB42D0A27, 0xECE67C0F,
0x302925F0, 0x6114F839, 0xD39E6307, 0xE28970D6,
0xEB982F99, 0x941B4CDF, 0xC540E550, 0x8124FC45,
0x98B025C7, 0xE2BF90EA, 0x4F57C976, 0xCF546FE4,
0x59566DC8, 0xE3F4360D, 0xF5F9D231, 0xD6180B22,
0xB54E088A, 0xB5DFE6A6, 0x3637A36F, 0x056E9284,
0xAFF8FBC5, 0x19E01648, 0x8611F043, 0xDAE44337,
0xF61B6A1C, 0x257ACD9E, 0xDD35F507, 0xEF05CAFA,
0x05EB4A83, 0xFC25CA92, 0x0A4728E6, 0x9CF150EF,
0xAEEF67DE, 0xA9472337, 0x57C81EFE, 0x3E5E009F,
0x02CB03BB, 0x2BA85674, 0xF21DC251, 0x78C34A34,
0xABB1F5BF, 0xB95A2FBD, 0x1FB47777, 0x9A96E8AC,
0x5D2D2838, 0x55AAC92A, 0x99EE324E, 0x10F6214B,
0x58ABDFB1, 0x2008794D, 0xBEC880F0, 0xE75E5341,
0x88015C34, 0x352D8FBF, 0x622B7F6C, 0xF5C59EA2,
0x1F759D8E, 0xADE56159, 0xCC7B4C25, 0x5B8BC48C,
0xB6BD15AF, 0x3C5B5110, 0xE74A7C3D, 0xEE613161,
0x156A1C67, 0x72C06817, 0xEA0A6F69, 0x4CECF993,
0xCA9D554C, 0x8E20361F, 0x42D396B9, 0x595DE578,
0x749D7955, 0xFD1BA5FD, 0x81FC160E, 0xDB97E28C,
0x7CF148F7, 0x0B0B3CF5, 0x534DE605, 0x46421066,
0xD4B68DD1, 0x9E479CE6, 0xAE667A9D, 0xBC082082,
0xB06DD6EF, 0x20F0F23F, 0xB99E1551, 0xF47A2E3A,
0x71DA50C6, 0x67B65779, 0x2A8CB376, 0x1EA71EEE,
0x29ABCD50, 0xB6EB0C6B, 0x23C10511, 0x6F3F2144,
0x6AF23012, 0xF696BD9E, 0xB94099D8, 0xAD5A9C81,
0x7A0794FA, 0x7EDF59D6, 0x1E72E574, 0x8561913C,
0x4E4D568F, 0xEECB9928, 0x9C124D2E, 0x0848B82C,
0xF1CA395F, 0x9DAF43DC, 0xF77EC323, 0x394E9B59,
0x7E200946, 0x8B811D68, 0x16DA3305, 0xAB8DE2C3,
0xE6C53B64, 0x98C2D321, 0x88A97D81, 0xA7106419,
0x8E52F7BF, 0x8ED262AF, 0x7CCA974E, 0xF0933241,
0x040DD437, 0xE143B3D4, 0x3019F56F, 0xB741521D,
0xF1745362, 0x4C435F9F, 0xB4214D0D, 0x0B0C348B,
0x5051D189, 0x4C30447E, 0x7393D722, 0x95CEDD0B,
0xDD994E80, 0xC3D22ED9, 0x739CD900, 0x131EB9C4,
0xEF1062B2, 0x4F0DE436, 0x52920073, 0x9A7F3D80,
0x896E7B1B, 0x2C8BBE5A, 0xBD304F8A, 0xA993E22C,
0x134C41A0, 0xFA989E00, 0x39CE9726, 0xFB89FCCF,
0xE8FBAC97, 0xD4063FFC, 0x935A2B5A, 0x44C8EE83,
0xCB2BC7B6, 0x02989E92, 0x75478BEA, 0x144378D0,
0xD853C087, 0x8897A34E, 0xDD23629D, 0xBDE2A2A2,
0x581D8ECC, 0x5DA8AEE8, 0xFF8AAFD0, 0xBA2BCF6E,
0x4BD98DAC, 0xF2EDB9E4, 0xFA2DC868, 0x47E84661,
0xECEB1C7D, 0x41705CA4, 0x5982E4D4, 0xEB5204A1,
0xD196CAFB, 0x6414804D, 0x3ABD4B46, 0x8B494C26,
0xB432D52B, 0x39C5356B, 0x6EC80BF7, 0x71BE5483,
0xCEC4A509, 0xE9411D61, 0x52F341E5, 0xD2E6197B,
0x4F02826C, 0xA9E48838, 0xD1F8F247, 0xE4957FB3,
0x586CCA99, 0x9A8B6A5B, 0x4998FBEA, 0xF762BE4C,
0x90DFE33C, 0x9731511E, 0x88C6A82F, 0xDD65A4D4
};
/// <summary> /// <summary>
/// Convert a hexadecimal string to a byte array. The input string must be /// Convert a hexadecimal string to a byte array. The input string must be
@@ -143,11 +89,11 @@ namespace KeePassLib.Utility
ch = strHex[i + 1]; ch = strHex[i + 1];
if ((ch >= '0') && (ch <= '9')) if ((ch >= '0') && (ch <= '9'))
bt += (byte)(ch - '0'); bt |= (byte)(ch - '0');
else if ((ch >= 'a') && (ch <= 'f')) else if ((ch >= 'a') && (ch <= 'f'))
bt += (byte)(ch - 'a' + 10); bt |= (byte)(ch - 'a' + 10);
else if ((ch >= 'A') && (ch <= 'F')) else if ((ch >= 'A') && (ch <= 'F'))
bt += (byte)(ch - 'A' + 10); bt |= (byte)(ch - 'A' + 10);
else { Debug.Assert(false); } else { Debug.Assert(false); }
pb[i >> 1] = bt; pb[i >> 1] = bt;
@@ -246,20 +192,26 @@ namespace KeePassLib.Utility
return l.ToArray(); return l.ToArray();
} }
internal static byte[] ParseBase32(string str, bool bAutoPad)
{
if (str == null) { Debug.Assert(false); return null; }
// https://sourceforge.net/p/keepass/discussion/329220/thread/59b61fddea/
if (bAutoPad && ((str.Length % 8) != 0))
str = str.PadRight((str.Length & ~7) + 8, '=');
return ParseBase32(str);
}
/// <summary> /// <summary>
/// Set all bytes in a byte array to zero. /// Set all bytes in a byte array to zero.
/// </summary> /// </summary>
/// <param name="pbArray">Input array. All bytes of this array /// <param name="pbArray">Input array. All bytes of this array
/// will be set to zero.</param> /// will be set to zero.</param>
#if KeePassLibSD [MethodImpl(MioNoOptimize)]
[MethodImpl(MethodImplOptions.NoInlining)]
#else
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
#endif
public static void ZeroByteArray(byte[] pbArray) public static void ZeroByteArray(byte[] pbArray)
{ {
Debug.Assert(pbArray != null); if (pbArray == null) { Debug.Assert(false); return; }
if(pbArray == null) throw new ArgumentNullException("pbArray");
Array.Clear(pbArray, 0, pbArray.Length); Array.Clear(pbArray, 0, pbArray.Length);
} }
@@ -268,18 +220,41 @@ namespace KeePassLib.Utility
/// Set all elements of an array to the default value. /// Set all elements of an array to the default value.
/// </summary> /// </summary>
/// <param name="v">Input array.</param> /// <param name="v">Input array.</param>
#if KeePassLibSD [MethodImpl(MioNoOptimize)]
[MethodImpl(MethodImplOptions.NoInlining)]
#else
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
#endif
public static void ZeroArray<T>(T[] v) public static void ZeroArray<T>(T[] v)
{ {
if(v == null) { Debug.Assert(false); throw new ArgumentNullException("v"); } if (v == null) { Debug.Assert(false); return; }
Array.Clear(v, 0, v.Length); Array.Clear(v, 0, v.Length);
} }
private static byte[] g_pbZero = null;
[MethodImpl(MioNoOptimize)]
public static void ZeroMemory(IntPtr pb, long cb)
{
if (pb == IntPtr.Zero) { Debug.Assert(false); return; }
if (cb < 0) { Debug.Assert(false); return; }
byte[] pbZero = g_pbZero;
if (pbZero == null)
{
pbZero = new byte[4096];
g_pbZero = pbZero;
}
long cbZero = pbZero.Length;
while (cb != 0)
{
long cbBlock = Math.Min(cb, cbZero);
Marshal.Copy(pbZero, 0, pb, (int)cbBlock);
pb = AddPtr(pb, cbBlock);
cb -= cbBlock;
}
}
/// <summary> /// <summary>
/// Convert 2 bytes to a 16-bit unsigned integer (little-endian). /// Convert 2 bytes to a 16-bit unsigned integer (little-endian).
/// </summary> /// </summary>
@@ -396,15 +371,7 @@ namespace KeePassLib.Utility
/// </summary> /// </summary>
public static byte[] UInt16ToBytes(ushort uValue) public static byte[] UInt16ToBytes(ushort uValue)
{ {
byte[] pb = new byte[2]; return new byte[2] { (byte)uValue, (byte)(uValue >> 8) };
unchecked
{
pb[0] = (byte)uValue;
pb[1] = (byte)(uValue >> 8);
}
return pb;
} }
/// <summary> /// <summary>
@@ -412,17 +379,8 @@ namespace KeePassLib.Utility
/// </summary> /// </summary>
public static byte[] UInt32ToBytes(uint uValue) public static byte[] UInt32ToBytes(uint uValue)
{ {
byte[] pb = new byte[4]; return new byte[4] { (byte)uValue, (byte)(uValue >> 8),
(byte)(uValue >> 16), (byte)(uValue >> 24) };
unchecked
{
pb[0] = (byte)uValue;
pb[1] = (byte)(uValue >> 8);
pb[2] = (byte)(uValue >> 16);
pb[3] = (byte)(uValue >> 24);
}
return pb;
} }
/// <summary> /// <summary>
@@ -431,41 +389,27 @@ namespace KeePassLib.Utility
public static void UInt32ToBytesEx(uint uValue, byte[] pb, int iOffset) public static void UInt32ToBytesEx(uint uValue, byte[] pb, int iOffset)
{ {
if (pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } if (pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); }
if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) if ((iOffset < 0) || (iOffset >= (pb.Length - 3)))
{ {
Debug.Assert(false); Debug.Assert(false);
throw new ArgumentOutOfRangeException("iOffset"); throw new ArgumentOutOfRangeException("iOffset");
} }
unchecked
{
pb[iOffset] = (byte)uValue; pb[iOffset] = (byte)uValue;
pb[iOffset + 1] = (byte)(uValue >> 8); pb[iOffset + 1] = (byte)(uValue >> 8);
pb[iOffset + 2] = (byte)(uValue >> 16); pb[iOffset + 2] = (byte)(uValue >> 16);
pb[iOffset + 3] = (byte)(uValue >> 24); pb[iOffset + 3] = (byte)(uValue >> 24);
} }
}
/// <summary> /// <summary>
/// Convert a 64-bit unsigned integer to 8 bytes (little-endian). /// Convert a 64-bit unsigned integer to 8 bytes (little-endian).
/// </summary> /// </summary>
public static byte[] UInt64ToBytes(ulong uValue) public static byte[] UInt64ToBytes(ulong uValue)
{ {
byte[] pb = new byte[8]; return new byte[8] { (byte)uValue, (byte)(uValue >> 8),
(byte)(uValue >> 16), (byte)(uValue >> 24),
unchecked (byte)(uValue >> 32), (byte)(uValue >> 40),
{ (byte)(uValue >> 48), (byte)(uValue >> 56) };
pb[0] = (byte)uValue;
pb[1] = (byte)(uValue >> 8);
pb[2] = (byte)(uValue >> 16);
pb[3] = (byte)(uValue >> 24);
pb[4] = (byte)(uValue >> 32);
pb[5] = (byte)(uValue >> 40);
pb[6] = (byte)(uValue >> 48);
pb[7] = (byte)(uValue >> 56);
}
return pb;
} }
/// <summary> /// <summary>
@@ -474,14 +418,12 @@ namespace KeePassLib.Utility
public static void UInt64ToBytesEx(ulong uValue, byte[] pb, int iOffset) public static void UInt64ToBytesEx(ulong uValue, byte[] pb, int iOffset)
{ {
if (pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } if (pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); }
if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) if ((iOffset < 0) || (iOffset >= (pb.Length - 7)))
{ {
Debug.Assert(false); Debug.Assert(false);
throw new ArgumentOutOfRangeException("iOffset"); throw new ArgumentOutOfRangeException("iOffset");
} }
unchecked
{
pb[iOffset] = (byte)uValue; pb[iOffset] = (byte)uValue;
pb[iOffset + 1] = (byte)(uValue >> 8); pb[iOffset + 1] = (byte)(uValue >> 8);
pb[iOffset + 2] = (byte)(uValue >> 16); pb[iOffset + 2] = (byte)(uValue >> 16);
@@ -491,18 +433,27 @@ namespace KeePassLib.Utility
pb[iOffset + 6] = (byte)(uValue >> 48); pb[iOffset + 6] = (byte)(uValue >> 48);
pb[iOffset + 7] = (byte)(uValue >> 56); pb[iOffset + 7] = (byte)(uValue >> 56);
} }
}
public static byte[] Int32ToBytes(int iValue) public static byte[] Int32ToBytes(int iValue)
{ {
return UInt32ToBytes((uint)iValue); return UInt32ToBytes((uint)iValue);
} }
public static void Int32ToBytesEx(int iValue, byte[] pb, int iOffset)
{
UInt32ToBytesEx((uint)iValue, pb, iOffset);
}
public static byte[] Int64ToBytes(long lValue) public static byte[] Int64ToBytes(long lValue)
{ {
return UInt64ToBytes((ulong)lValue); return UInt64ToBytes((ulong)lValue);
} }
public static void Int64ToBytesEx(long lValue, byte[] pb, int iOffset)
{
UInt64ToBytesEx((ulong)lValue, pb, iOffset);
}
public static uint RotateLeft32(uint u, int nBits) public static uint RotateLeft32(uint u, int nBits)
{ {
return ((u << nBits) | (u >> (32 - nBits))); return ((u << nBits) | (u >> (32 - nBits)));
@@ -523,14 +474,35 @@ namespace KeePassLib.Utility
return ((u >> nBits) | (u << (64 - nBits))); return ((u >> nBits) | (u << (64 - nBits)));
} }
private static void AddVersionComponent(ref ulong uVersion, int iValue)
{
if (iValue < 0) iValue = 0;
else if (iValue > 0xFFFF) { Debug.Assert(false); iValue = 0xFFFF; }
uVersion = (uVersion << 16) | (uint)iValue;
}
internal static ulong VersionToUInt64(Version v)
{
if (v == null) { Debug.Assert(false); return 0; }
ulong u = 0;
AddVersionComponent(ref u, v.Major);
AddVersionComponent(ref u, v.Minor);
AddVersionComponent(ref u, v.Build);
AddVersionComponent(ref u, v.Revision);
return u;
}
public static bool ArraysEqual(byte[] x, byte[] y) public static bool ArraysEqual(byte[] x, byte[] y)
{ {
// Return false if one of them is null (not comparable)! // Return false if one of them is null (not comparable)!
if ((x == null) || (y == null)) { Debug.Assert(false); return false; } if ((x == null) || (y == null)) { Debug.Assert(false); return false; }
if(x.Length != y.Length) return false; int cb = x.Length;
if (cb != y.Length) return false;
for(int i = 0; i < x.Length; ++i) for (int i = 0; i < cb; ++i)
{ {
if (x[i] != y[i]) return false; if (x[i] != y[i]) return false;
} }
@@ -556,28 +528,90 @@ namespace KeePassLib.Utility
} }
/// <summary> /// <summary>
/// Fast hash that can be used e.g. for hash tables. /// Fast 32-bit hash (e.g. for hash tables).
/// The algorithm might change in the future; do not store /// The algorithm might change in the future; do not store
/// the hashes for later use. /// the hashes for later use.
/// </summary> /// </summary>
public static uint Hash32(byte[] v, int iStart, int iLength) public static uint Hash32(byte[] pb, int iOffset, int cb)
{ {
uint u = 0x326F637B; const ulong hI = 0x4295DC458269ED9DUL;
const uint hI32 = (uint)(hI >> 32);
if(v == null) { Debug.Assert(false); return u; } if (pb == null) { Debug.Assert(false); return hI32; }
if(iStart < 0) { Debug.Assert(false); return u; } if (iOffset < 0) { Debug.Assert(false); return hI32; }
if(iLength < 0) { Debug.Assert(false); return u; } if (cb < 0) { Debug.Assert(false); return hI32; }
int m = iStart + iLength; int m = iOffset + cb;
if(m > v.Length) { Debug.Assert(false); return u; } if ((m < 0) || (m > pb.Length)) { Debug.Assert(false); return hI32; }
for(int i = iStart; i < m; ++i) int m4 = iOffset + (cb & ~3), cbR = cb & 3;
ulong h = hI;
for (int i = iOffset; i < m4; i += 4)
h = (pb[i] ^ ((ulong)pb[i + 1] << 8) ^ ((ulong)pb[i + 2] << 16) ^
((ulong)pb[i + 3] << 24) ^ h) * 0x5EA4A1E35C8ACDA3UL;
switch (cbR)
{ {
u ^= m_vSBox[v[i]]; case 1:
u *= 3; Debug.Assert(m4 == (m - 1));
h = (pb[m4] ^ h) * 0x54A1CC5970AF27BBUL;
break;
case 2:
Debug.Assert(m4 == (m - 2));
h = (pb[m4] ^ ((ulong)pb[m4 + 1] << 8) ^ h) *
0x6C45CB2537A4271DUL;
break;
case 3:
Debug.Assert(m4 == (m - 3));
h = (pb[m4] ^ ((ulong)pb[m4 + 1] << 8) ^
((ulong)pb[m4 + 2] << 16) ^ h) * 0x59B8E8939E19695DUL;
break;
default:
Debug.Assert(m4 == m);
break;
} }
return u; Debug.Assert((cb != 0) || ((uint)(h >> 32) == hI32));
return (uint)(h >> 32);
}
internal static uint Hash32Ex<T>(T[] v, int iOffset, int c)
{
const ulong hI = 0x4295DC458269ED9DUL;
const uint hI32 = (uint)(hI >> 32);
if (v == null) { Debug.Assert(false); return hI32; }
if (iOffset < 0) { Debug.Assert(false); return hI32; }
if (c < 0) { Debug.Assert(false); return hI32; }
int m = iOffset + c;
if ((m < 0) || (m > v.Length)) { Debug.Assert(false); return hI32; }
ulong h = hI;
for (int i = iOffset; i < m; ++i)
h = (h ^ (uint)v[i].GetHashCode()) * 0x5EA4A1E35C8ACDA3UL;
Debug.Assert((c != 0) || ((uint)(h >> 32) == hI32));
return (uint)(h >> 32);
}
internal static ulong Hash64(int[] v, int iOffset, int ci)
{
ulong h = 0x4295DC458269ED9DUL;
if (v == null) { Debug.Assert(false); return h; }
if (iOffset < 0) { Debug.Assert(false); return h; }
if (ci < 0) { Debug.Assert(false); return h; }
int m = iOffset + ci;
if ((m < 0) || (m > v.Length)) { Debug.Assert(false); return h; }
for (int i = iOffset; i < m; ++i)
h = (h ^ (uint)v[i]) * 0x5EA4A1E35C8ACDA3UL;
return ((h ^ (h >> 32)) * 0x59B8E8939E19695DUL);
} }
public static void CopyStream(Stream sSource, Stream sTarget) public static void CopyStream(Stream sSource, Stream sTarget)
@@ -586,20 +620,31 @@ namespace KeePassLib.Utility
if (sSource == null) throw new ArgumentNullException("sSource"); if (sSource == null) throw new ArgumentNullException("sSource");
if (sTarget == null) throw new ArgumentNullException("sTarget"); if (sTarget == null) throw new ArgumentNullException("sTarget");
const int nBufSize = 4096; const int cbBuf = 4096;
byte[] pbBuf = new byte[nBufSize]; byte[] pbBuf = new byte[cbBuf];
while (true) while (true)
{ {
int nRead = sSource.Read(pbBuf, 0, nBufSize); int cbRead = sSource.Read(pbBuf, 0, cbBuf);
if(nRead == 0) break; if (cbRead == 0) break;
sTarget.Write(pbBuf, 0, nRead); sTarget.Write(pbBuf, 0, cbRead);
} }
// Do not close any of the streams // Do not close any of the streams
} }
public static byte[] Read(Stream s)
{
if (s == null) throw new ArgumentNullException("s");
using (MemoryStream ms = new MemoryStream())
{
CopyStream(s, ms);
return ms.ToArray();
}
}
public static byte[] Read(Stream s, int nCount) public static byte[] Read(Stream s, int nCount)
{ {
if (s == null) throw new ArgumentNullException("s"); if (s == null) throw new ArgumentNullException("s");
@@ -626,6 +671,17 @@ namespace KeePassLib.Utility
return pb; return pb;
} }
internal static string ReadString(Stream s, Encoding enc)
{
if (s == null) throw new ArgumentNullException("s");
if (enc == null) throw new ArgumentNullException("enc");
using (StreamReader sr = new StreamReader(s, enc, true))
{
return sr.ReadToEnd();
}
}
public static void Write(Stream s, byte[] pbData) public static void Write(Stream s, byte[] pbData)
{ {
if (s == null) { Debug.Assert(false); return; } if (s == null) { Debug.Assert(false); return; }
@@ -640,7 +696,6 @@ namespace KeePassLib.Utility
if (pbData == null) throw new ArgumentNullException("pbData"); if (pbData == null) throw new ArgumentNullException("pbData");
if (pbData.Length == 0) return pbData; if (pbData.Length == 0) return pbData;
byte[] pbCompressed;
using (MemoryStream msSource = new MemoryStream(pbData, false)) using (MemoryStream msSource = new MemoryStream(pbData, false))
{ {
using (MemoryStream msCompressed = new MemoryStream()) using (MemoryStream msCompressed = new MemoryStream())
@@ -648,14 +703,12 @@ namespace KeePassLib.Utility
using (GZipStream gz = new GZipStream(msCompressed, using (GZipStream gz = new GZipStream(msCompressed,
CompressionMode.Compress)) CompressionMode.Compress))
{ {
MemUtil.CopyStream(msSource, gz); CopyStream(msSource, gz);
} }
pbCompressed = msCompressed.ToArray(); return msCompressed.ToArray();
} }
} }
return pbCompressed;
} }
public static byte[] Decompress(byte[] pbCompressed) public static byte[] Decompress(byte[] pbCompressed)
@@ -663,7 +716,6 @@ namespace KeePassLib.Utility
if (pbCompressed == null) throw new ArgumentNullException("pbCompressed"); if (pbCompressed == null) throw new ArgumentNullException("pbCompressed");
if (pbCompressed.Length == 0) return pbCompressed; if (pbCompressed.Length == 0) return pbCompressed;
byte[] pbData;
using (MemoryStream msData = new MemoryStream()) using (MemoryStream msData = new MemoryStream())
{ {
using (MemoryStream msCompressed = new MemoryStream(pbCompressed, false)) using (MemoryStream msCompressed = new MemoryStream(pbCompressed, false))
@@ -671,14 +723,12 @@ namespace KeePassLib.Utility
using (GZipStream gz = new GZipStream(msCompressed, using (GZipStream gz = new GZipStream(msCompressed,
CompressionMode.Decompress)) CompressionMode.Decompress))
{ {
MemUtil.CopyStream(gz, msData); CopyStream(gz, msData);
} }
} }
pbData = msData.ToArray(); return msData.ToArray();
} }
return pbData;
} }
public static int IndexOf<T>(T[] vHaystack, T[] vNeedle) public static int IndexOf<T>(T[] vHaystack, T[] vNeedle)
@@ -688,10 +738,13 @@ namespace KeePassLib.Utility
if (vNeedle == null) throw new ArgumentNullException("vNeedle"); if (vNeedle == null) throw new ArgumentNullException("vNeedle");
if (vNeedle.Length == 0) return 0; if (vNeedle.Length == 0) return 0;
for(int i = 0; i <= (vHaystack.Length - vNeedle.Length); ++i) int cN = vNeedle.Length;
int iMax = vHaystack.Length - cN;
for (int i = 0; i <= iMax; ++i)
{ {
bool bFound = true; bool bFound = true;
for(int m = 0; m < vNeedle.Length; ++m) for (int m = 0; m < cN; ++m)
{ {
if (!vHaystack[i + m].Equals(vNeedle[m])) if (!vHaystack[i + m].Equals(vNeedle[m]))
{ {
@@ -786,6 +839,44 @@ namespace KeePassLib.Utility
yield break; yield break;
} }
internal static IEnumerable<T> Distinct<T, TKey>(IEnumerable<T> s,
GFunc<T, TKey> fGetKey, bool bPreferLast)
{
if (s == null) throw new ArgumentNullException("s");
if (fGetKey == null) throw new ArgumentNullException("fGetKey");
Dictionary<TKey, bool> d = new Dictionary<TKey, bool>();
if (bPreferLast)
{
List<T> l = new List<T>(s);
int n = l.Count;
bool[] v = new bool[n];
for (int i = n - 1; i >= 0; --i)
{
TKey k = fGetKey(l[i]);
if (!d.ContainsKey(k)) { d[k] = true; v[i] = true; }
}
for (int i = 0; i < n; ++i)
{
if (v[i]) yield return l[i];
}
}
else
{
foreach (T t in s)
{
TKey k = fGetKey(t);
if (!d.ContainsKey(k)) { d[k] = true; yield return t; }
}
}
yield break;
}
internal static bool ListsEqual<T>(List<T> a, List<T> b) internal static bool ListsEqual<T>(List<T> a, List<T> b)
where T : class, IEquatable<T> where T : class, IEquatable<T>
{ {
@@ -809,5 +900,164 @@ namespace KeePassLib.Utility
return true; return true;
} }
internal static int Count(byte[] pb, byte bt)
{
if (pb == null) { Debug.Assert(false); return 0; }
int cb = pb.Length, r = 0;
for (int i = 0; i < cb; ++i)
{
if (pb[i] == bt) ++r;
}
return r;
}
[MethodImpl(MioNoOptimize)]
internal static void DisposeIfPossible(object o)
{
if (o == null) { Debug.Assert(false); return; }
IDisposable d = (o as IDisposable);
if (d != null) d.Dispose();
}
internal static object GetEnumValue(Type tEnum, string strName)
{
if (tEnum == null) { Debug.Assert(false); return null; }
if (!tEnum.IsEnum) { Debug.Assert(false); return null; }
if (string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; }
return ((Array.IndexOf<string>(Enum.GetNames(tEnum), strName) >= 0) ?
Enum.Parse(tEnum, strName) : null);
}
internal static T ConvertObject<T>(object o, T tDefault)
{
if (o == null) return tDefault;
try
{
if (o is T) return (T)o;
return (T)Convert.ChangeType(o, typeof(T));
}
catch (Exception) { Debug.Assert(false); }
try { return (T)o; }
catch (Exception) { Debug.Assert(false); }
return tDefault;
}
internal static T BytesToStruct<T>(byte[] pb, int iOffset)
where T : struct
{
if (pb == null) throw new ArgumentNullException("pb");
if (iOffset < 0) throw new ArgumentOutOfRangeException("iOffset");
int cb = Marshal.SizeOf(typeof(T));
if (cb <= 0) { Debug.Assert(false); return default(T); }
if (iOffset > (pb.Length - cb)) throw new ArgumentOutOfRangeException("iOffset");
IntPtr p = Marshal.AllocCoTaskMem(cb);
if (p == IntPtr.Zero) throw new OutOfMemoryException();
object o;
try
{
Marshal.Copy(pb, iOffset, p, cb);
o = Marshal.PtrToStructure(p, typeof(T));
}
finally { Marshal.FreeCoTaskMem(p); }
return (T)o;
}
internal static byte[] StructToBytes<T>(ref T t)
where T : struct
{
int cb = Marshal.SizeOf(typeof(T));
if (cb <= 0) { Debug.Assert(false); return MemUtil.EmptyByteArray; }
byte[] pb = new byte[cb];
IntPtr p = Marshal.AllocCoTaskMem(cb);
if (p == IntPtr.Zero) throw new OutOfMemoryException();
try
{
Marshal.StructureToPtr(t, p, false);
Marshal.Copy(p, pb, 0, cb);
}
finally { Marshal.FreeCoTaskMem(p); }
return pb;
}
internal static IntPtr AddPtr(IntPtr p, long cb)
{
// IntPtr.operator+ and IntPtr.Add are not available in .NET 2.0
if (IntPtr.Size >= 8)
return new IntPtr(unchecked(p.ToInt64() + cb));
return new IntPtr(unchecked(p.ToInt32() + (int)cb));
}
// Cf. Array.Empty<T>() of .NET 4.6
private static class EmptyArrayEx<T>
{
internal static readonly T[] Instance = new T[0];
}
internal static T[] EmptyArray<T>()
{
return EmptyArrayEx<T>.Instance;
}
}
internal sealed class ArrayHelperEx<T> : IEqualityComparer<T[]>, IComparer<T[]>
where T : IEquatable<T>, IComparable<T>
{
public int GetHashCode(T[] obj)
{
if (obj == null) { Debug.Assert(false); throw new ArgumentNullException("obj"); }
return (int)MemUtil.Hash32Ex<T>(obj, 0, obj.Length);
}
public bool Equals(T[] x, T[] y)
{
if (object.ReferenceEquals(x, y)) return true;
if ((x == null) || (y == null)) return false;
int n = x.Length;
if (n != y.Length) return false;
for (int i = 0; i < n; ++i)
{
if (!x[i].Equals(y[i])) return false;
}
return true;
}
public int Compare(T[] x, T[] y)
{
if (object.ReferenceEquals(x, y)) return 0;
if (x == null) return -1;
if (y == null) return 1;
int n = x.Length, m = y.Length;
if (n != m) return ((n < m) ? -1 : 1);
for (int i = 0; i < n; ++i)
{
T tX = x[i], tY = y[i];
if (!tX.Equals(tY)) return tX.CompareTo(tY);
}
return 0;
}
} }
} }

View File

@@ -8,6 +8,7 @@ using Android.Content;
using Android.OS; using Android.OS;
using FluentFTP; using FluentFTP;
using FluentFTP.Exceptions; using FluentFTP.Exceptions;
using FluentFTP.GnuTLS;
using KeePass.Util; using KeePass.Util;
using KeePassLib; using KeePassLib;
using KeePassLib.Serialization; using KeePassLib.Serialization;
@@ -140,6 +141,7 @@ namespace keepass2android.Io
var settings = ConnectionSettings.FromIoc(ioc); var settings = ConnectionSettings.FromIoc(ioc);
FtpClient client = new FtpClient(); FtpClient client = new FtpClient();
client.Config.CustomStream = typeof(GnuTlsStream);
client.Config.RetryAttempts = 3; client.Config.RetryAttempts = 3;
if ((settings.Username.Length > 0) || (settings.Password.Length > 0)) if ((settings.Username.Length > 0) || (settings.Password.Length > 0))
client.Credentials = new NetworkCredential(settings.Username, settings.Password); client.Credentials = new NetworkCredential(settings.Username, settings.Password);

View File

@@ -10,7 +10,8 @@
<Folder Include="Resources\" /> <Folder Include="Resources\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentFTP" Version="51.1.0" Condition="'$(Flavor)'!='NoNet'"/> <PackageReference Include="FluentFTP" Version="52.1.0" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="FluentFTP.GnuTLS" Version="1.0.37" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="MegaApiClient" Version="1.10.4" Condition="'$(Flavor)'!='NoNet'"/> <PackageReference Include="MegaApiClient" Version="1.10.4" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="Microsoft.Graph" Version="5.68.0" Condition="'$(Flavor)'!='NoNet'"/> <PackageReference Include="Microsoft.Graph" Version="5.68.0" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" Condition="'$(Flavor)'!='NoNet'"/> <PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" Condition="'$(Flavor)'!='NoNet'"/>

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,8 @@ namespace keepass2android
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title)); builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title));
List<string> changeLog = new List<string>{ List<string> changeLog = new List<string>{
BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_13}, "1.13"),
BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_12 BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_12
#if !NoNet #if !NoNet
,Resource.Array.ChangeLog_1_12_net ,Resource.Array.ChangeLog_1_12_net

View File

@@ -1546,10 +1546,10 @@ namespace keepass2android
string url = _stringViews[urlFieldKey].Text; string url = _stringViews[urlFieldKey].Text;
if (url == null) return false; if (url == null) return false;
// Default http:// if no protocol specified // Default https:// if no protocol specified
if ((!url.Contains(":") || (url.StartsWith("www.")))) if ((!url.Contains(":") || (url.StartsWith("www."))))
{ {
url = "http://" + url; url = "https://" + url;
} }
try try

View File

@@ -23,6 +23,7 @@ using Android.App;
using Android.App.Admin; using Android.App.Admin;
using Android.Content; using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics; using Android.Graphics;
using Android.OS; using Android.OS;
using Android.Preferences; using Android.Preferences;
@@ -66,8 +67,13 @@ namespace keepass2android
Resource.Id.cb_exclude_lookalike Resource.Id.cb_exclude_lookalike
}; };
PasswordFont _passwordFont = new PasswordFont(); PasswordFont _passwordFont = new PasswordFont();
private static object _popularPasswordsLock = new object();
private static bool _popularPasswordsInitialized = false;
private ActivityDesign _design; private ActivityDesign _design;
public GeneratePasswordActivity() public GeneratePasswordActivity()
@@ -302,6 +308,10 @@ namespace keepass2android
EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit); EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit);
txtPasswordToSet.TextChanged += (sender, args) =>
{
Task.Run(() => UpdatePasswordStrengthEstimate(txtPasswordToSet.Text));
};
_passwordFont.ApplyTo(txtPasswordToSet); _passwordFont.ApplyTo(txtPasswordToSet);
@@ -467,17 +477,48 @@ namespace keepass2android
return; return;
String password = ""; String password = "";
uint passwordBits = 0;
Task.Run(() => Task.Run(() =>
{ {
password = GeneratePassword(); password = GeneratePassword();
passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray());
RunOnUiThread(() => RunOnUiThread(() =>
{ {
EditText txtPassword = (EditText)FindViewById(Resource.Id.password_edit); EditText txtPassword = (EditText)FindViewById(Resource.Id.password_edit);
txtPassword.Text = password; txtPassword.Text = password;
UpdateProfileSpinnerSelection();
});
});
}
private void UpdatePasswordStrengthEstimate(string password)
{
lock (_popularPasswordsLock)
{
if (!_popularPasswordsInitialized)
{
using (StreamReader sr = new StreamReader(Assets.Open("MostPopularPasswords.txt")))
{
var bytes = default(byte[]);
using (var memstream = new MemoryStream())
{
sr.BaseStream.CopyTo(memstream);
bytes = memstream.ToArray();
}
PopularPasswords.Add(bytes, false);
}
}
}
uint passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray());
RunOnUiThread(() =>
{
var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength); var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength);
progressBar.Progress = (int)passwordBits; progressBar.Progress = (int)passwordBits;
@@ -503,13 +544,7 @@ namespace keepass2android
PorterDuff.Mode.SrcIn)); PorterDuff.Mode.SrcIn));
FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits"; FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits";
UpdateProfileSpinnerSelection();
}); });
});
} }
private void UpdateProfileSpinnerSelection() private void UpdateProfileSpinnerSelection()

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="217" android:versionCode="222"
android:versionName="1.12-r9c" android:versionName="1.13-r0"
package="keepass2android.keepass2android" package="keepass2android.keepass2android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto"> android:installLocation="auto">

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="217" android:versionCode="222"
android:versionName="1.12-r9c" android:versionName="1.13-r0"
package="keepass2android.keepass2android_nonet" package="keepass2android.keepass2android_nonet"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto"> android:installLocation="auto">

View File

@@ -1423,6 +1423,8 @@ namespace keepass2android
if (cbQuickUnlock == null) if (cbQuickUnlock == null)
throw new NullPointerException("cpQuickUnlock"); throw new NullPointerException("cpQuickUnlock");
App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked); App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase =
(((KeyguardManager)GetSystemService(Context.KeyguardService)!)!).IsDeviceSecure;
if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline)) if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline))
{ {

View File

@@ -25,6 +25,7 @@ using Android.Widget;
using Android.Content.PM; using Android.Content.PM;
using KeePassLib.Keys; using KeePassLib.Keys;
using Android.Preferences; using Android.Preferences;
using Android.Provider;
using Android.Runtime; using Android.Runtime;
using Android.Views.InputMethods; using Android.Views.InputMethods;
@@ -162,6 +163,29 @@ namespace keepass2android
if (bundle != null) if (bundle != null)
numFailedAttempts = bundle.GetInt(NumFailedAttemptsKey, 0); numFailedAttempts = bundle.GetInt(NumFailedAttemptsKey, 0);
FindViewById(Resource.Id.QuickUnlock_buttonEnableLock).Click += (object sender, EventArgs e) =>
{
Intent intent = new Intent(Settings.ActionSecuritySettings);
StartActivity(intent);
};
FindViewById(Resource.Id.QuickUnlock_buttonCloseDb).Click += (object sender, EventArgs e) =>
{
App.Kp2a.Lock(false);
};
if (App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase == false)
{
FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Visible;
}
else
{
FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Gone;
}
} }

View File

@@ -79,6 +79,12 @@ android:paddingRight="16dp"
android:paddingTop="16dp"> android:paddingTop="16dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/QuickUnlockForm">
<TextView <TextView
android:id="@+id/QuickUnlock_label" android:id="@+id/QuickUnlock_label"
android:text="@string/QuickUnlock_label" android:text="@string/QuickUnlock_label"
@@ -88,11 +94,6 @@ android:paddingRight="16dp"
android:textSize="14sp" android:textSize="14sp"
/> />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText <EditText
android:inputType="textPassword" android:inputType="textPassword"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -121,6 +122,60 @@ android:paddingRight="16dp"
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/md_theme_secondaryContainer"
android:id="@+id/QuickUnlockBlocked"
android:padding="16dp"
android:layout_gravity="center">
<TextView
android:id="@+id/quick_unlock_blocked_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_based_quick_unlock_not_available"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:paddingBottom="8dp"/>
<TextView
android:id="@+id/alert_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_based_quick_unlock_not_available_text"
android:textSize="16sp"
android:paddingBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/QuickUnlock_buttonEnableLock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:backgroundTint="@color/md_theme_secondary"
android:textColor="@android:color/white"
android:text="@string/enable_screen_lock"
android:fontFamily="sans-serif-medium" />
<Button
android:id="@+id/QuickUnlock_buttonCloseDb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:backgroundTint="@color/md_theme_secondary"
android:textColor="@android:color/white"
android:fontFamily="sans-serif-medium"
android:text="@string/QuickUnlock_lockButton" />
</LinearLayout>
</LinearLayout>
<View <View
android:id="@+id/spacing" android:id="@+id/spacing"

View File

@@ -176,7 +176,8 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="masktotp_title">TOTP-Feld verdecken</string> <string name="masktotp_title">TOTP-Feld verdecken</string>
<string name="masktotp_summary">TOTP-Feld standardmäßig ausblenden</string> <string name="masktotp_summary">TOTP-Feld standardmäßig ausblenden</string>
<string name="NoAutofillDisabling_title">Keine Option, um Autofill zu deaktivieren</string> <string name="NoAutofillDisabling_title">Keine Option, um Autofill zu deaktivieren</string>
<string name="NoAutofillDisabling_summary">Wenn aktiviert, wird die App die Option zum Abschalten von Autofill für bestimmte Einträge nicht anzeigen. </string> <string name="NoAutofillDisabling_summary">Wenn aktiviert, wird die App die Option zum Abschalten von Autofill für bestimmte Einträge nicht angezeigt.
</string>
<string name="menu_about">Über</string> <string name="menu_about">Über</string>
<string name="menu_change_key">Hauptschlüssel ändern</string> <string name="menu_change_key">Hauptschlüssel ändern</string>
<string name="menu_copy_pass">Passwort kopieren</string> <string name="menu_copy_pass">Passwort kopieren</string>
@@ -290,7 +291,7 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="please_note">Bitte beachten</string> <string name="please_note">Bitte beachten</string>
<string name="contributors">Mitwirkende</string> <string name="contributors">Mitwirkende</string>
<string name="regular_expression">Regulärer Ausdruck</string> <string name="regular_expression">Regulärer Ausdruck</string>
<string name="AlwaysMergeOnConflict_title">Bei Konflikt immer vereinigen</string> <string name="AlwaysMergeOnConflict_title">Bei Konflikt immer zusammenführen</string>
<string name="AlwaysMergeOnConflict_summary">Die lokalen Änderungen immer mit den entfernten Änderungen vereinigen, wenn Keepass2Android erkennt, dass die entfernte Datei verändert wurde.</string> <string name="AlwaysMergeOnConflict_summary">Die lokalen Änderungen immer mit den entfernten Änderungen vereinigen, wenn Keepass2Android erkennt, dass die entfernte Datei verändert wurde.</string>
<string name="TanExpiresOnUse_title">TAN verfällt bei Verwendung</string> <string name="TanExpiresOnUse_title">TAN verfällt bei Verwendung</string>
<string name="TanExpiresOnUse_summary">TAN-Einträge als abgelaufen markieren, wenn sie verwendet werden</string> <string name="TanExpiresOnUse_summary">TAN-Einträge als abgelaufen markieren, wenn sie verwendet werden</string>
@@ -332,7 +333,7 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="QuickUnlockLength_summary">Maximale Anzahl von Zeichen, die als QuickUnlock-Passwort verwendet werden.</string> <string name="QuickUnlockLength_summary">Maximale Anzahl von Zeichen, die als QuickUnlock-Passwort verwendet werden.</string>
<string name="QuickUnlockHideLength_title">Länge des QuickUnlock-Codes verstecken</string> <string name="QuickUnlockHideLength_title">Länge des QuickUnlock-Codes verstecken</string>
<string name="QuickUnlockHideLength_summary">Wenn aktiviert, wird die Länge des QuickUnlock-Codes nicht auf dem QuickUnlock-Bildschirm angezeigt.</string> <string name="QuickUnlockHideLength_summary">Wenn aktiviert, wird die Länge des QuickUnlock-Codes nicht auf dem QuickUnlock-Bildschirm angezeigt.</string>
<string name="QuickUnlockKeyFromDatabase_title">QuickUnlock-Taste aus dem Datenbankeintrag.</string> <string name="QuickUnlockKeyFromDatabase_title">QuickUnlock-Schlüssel aus Datenbankeintrag</string>
<string name="QuickUnlockKeyFromDatabase_summary">Wenn die aktive Datenbank einen Eintrag mit dem Titel „QuickUnlock“ in der Root-Gruppe enthält, wird das Passwort dieses Eintrags als QuickUnlock-Code verwendet.</string> <string name="QuickUnlockKeyFromDatabase_summary">Wenn die aktive Datenbank einen Eintrag mit dem Titel „QuickUnlock“ in der Root-Gruppe enthält, wird das Passwort dieses Eintrags als QuickUnlock-Code verwendet.</string>
<string name="QuickUnlock_fail">QuickUnlock fehlgeschlagen: falsches Passwort!</string> <string name="QuickUnlock_fail">QuickUnlock fehlgeschlagen: falsches Passwort!</string>
<string name="SaveAttachmentDialog_title">Anhang speichern</string> <string name="SaveAttachmentDialog_title">Anhang speichern</string>
@@ -759,7 +760,7 @@ Anbei einige Tipps, die bei der Diagnose des Problems helfen können:\n
<string-array name="ChangeLog_1_09d"> <string-array name="ChangeLog_1_09d">
<item>Unterstützung für das Ansehen, Entfernen und Wiederherstellen von Eintragssicherungen hinzugefügt</item> <item>Unterstützung für das Ansehen, Entfernen und Wiederherstellen von Eintragssicherungen hinzugefügt</item>
<item>Unterstützung für MEGA-Cloudspeicher hinzugefügt </item> <item>Unterstützung für MEGA-Cloudspeicher hinzugefügt </item>
<item>Unterstützung für Google Drive mit eingeschränktem Anwendungsbereich hinzugefügt</item> <item>Unterstützung für Google Drive mit eingeschränktem Zugriff hinzugefügt</item>
</string-array> </string-array>
<string-array name="ChangeLog_1_09c"> <string-array name="ChangeLog_1_09c">
<item>Google-Drive-Authentifizierung erneut implementiert, Google-Drive-Unterstützung wieder aktiviert </item> <item>Google-Drive-Authentifizierung erneut implementiert, Google-Drive-Unterstützung wieder aktiviert </item>

View File

@@ -719,6 +719,14 @@
<string name="EntryChannel_desc">Notificación para simplificar el acceso a la entrada seleccionada actualmente.</string> <string name="EntryChannel_desc">Notificación para simplificar el acceso a la entrada seleccionada actualmente.</string>
<string name="CloseDbAfterFailedAttempts">Cierre la base de datos después de tres intentos fallidos de desbloqueo biométrico.</string> <string name="CloseDbAfterFailedAttempts">Cierre la base de datos después de tres intentos fallidos de desbloqueo biométrico.</string>
<string name="WarnFingerprintInvalidated">¡Atención! La autenticación biométrica puede ser invalidada por Android, p. ej. después de añadir una nueva huella dactilar en los ajustes de su dispositivo. ¡Esté seguro de conocer siempre cómo desbloquear con su contraseña maestra!</string> <string name="WarnFingerprintInvalidated">¡Atención! La autenticación biométrica puede ser invalidada por Android, p. ej. después de añadir una nueva huella dactilar en los ajustes de su dispositivo. ¡Esté seguro de conocer siempre cómo desbloquear con su contraseña maestra!</string>
<string-array name="ChangeLog_1_12">
<item>Actualizado desde Xamarin Android a .net 8</item>
<item>Upgraded to Target SDK 34</item>
<item>Upgraded to Material 3 user interface</item>
<item>Improve autofill to work with Compose apps</item>
<item>Fix hostname matching in autofill and search</item>
<item>Fix issue with password generator</item>
</string-array>
<string-array name="ChangeLog_1_11"> <string-array name="ChangeLog_1_11">
<item>Añadidos botones de acción flotante para la búsqueda y vista general de TOTP (si las entradas TOTP están presentes).</item> <item>Añadidos botones de acción flotante para la búsqueda y vista general de TOTP (si las entradas TOTP están presentes).</item>
<item>Se ha mejorado la visualización de los campos TOTP añadiendo un indicador de tiempo de espera y mostrándolo de forma más destacada.</item> <item>Se ha mejorado la visualización de los campos TOTP añadiendo un indicador de tiempo de espera y mostrándolo de forma más destacada.</item>

View File

@@ -546,6 +546,7 @@
<string name="filestoragename_dropboxKP2A">Dropbox (folder KP2A)</string> <string name="filestoragename_dropboxKP2A">Dropbox (folder KP2A)</string>
<string name="filestoragehelp_dropboxKP2A">Jeżeli nie chcesz dać KP2A pełnego dostępu do wszystkich folderów Dropbox, możesz wybrać tę opcję. Aplikacja zażąda dostępu jedynie do folderu Aplikacje/Keepass2Android. Jest to szczególnie przydatne podczas tworzenia nowej bazy danych. Jeżeli już posiadasz bazę danych, kliknij w tę opcję, aby utworzyć folder, następnie umieść swój plik w folderze (ze swojego PC) i wybierz tę opcję ponownie, aby otworzyć plik.</string> <string name="filestoragehelp_dropboxKP2A">Jeżeli nie chcesz dać KP2A pełnego dostępu do wszystkich folderów Dropbox, możesz wybrać tę opcję. Aplikacja zażąda dostępu jedynie do folderu Aplikacje/Keepass2Android. Jest to szczególnie przydatne podczas tworzenia nowej bazy danych. Jeżeli już posiadasz bazę danych, kliknij w tę opcję, aby utworzyć folder, następnie umieść swój plik w folderze (ze swojego PC) i wybierz tę opcję ponownie, aby otworzyć plik.</string>
<string name="filestoragename_gdrive">Google Drive</string> <string name="filestoragename_gdrive">Google Drive</string>
<string name="filestoragehelp_gdrive">Uwaga: Google ogranicza dostęp do Dysku Google dla coraz większej liczby użytkowników. Jeśli wbudowana obsługa Dysku Google nie działa, skorzystaj z systemowego wybierania plików i wybierz Dysk Google stamtąd!</string>
<string name="filestoragename_gdriveKP2A">Dysk Google (pliki KP2A)</string> <string name="filestoragename_gdriveKP2A">Dysk Google (pliki KP2A)</string>
<string name="filestoragehelp_gdriveKP2A">Jeśli nie chcesz dać KP2A dostępu do pełnego Dysku Google, możesz wybrać tę opcję. Pamiętaj, że najpierw musisz utworzyć plik bazy danych, istniejące pliki nie są widoczne dla aplikacji. Wybierz tę opcję z ekranu tworzenia bazy danych lub, jeśli już otworzyłeś bazę danych, eksportując bazę danych wybierając tę opcję.</string> <string name="filestoragehelp_gdriveKP2A">Jeśli nie chcesz dać KP2A dostępu do pełnego Dysku Google, możesz wybrać tę opcję. Pamiętaj, że najpierw musisz utworzyć plik bazy danych, istniejące pliki nie są widoczne dla aplikacji. Wybierz tę opcję z ekranu tworzenia bazy danych lub, jeśli już otworzyłeś bazę danych, eksportując bazę danych wybierając tę opcję.</string>
<string name="filestoragename_pcloud">PCloud (KP2A folder)</string> <string name="filestoragename_pcloud">PCloud (KP2A folder)</string>
@@ -718,16 +719,33 @@
<string name="EntryChannel_desc">Powiadomienie ułatwiające dostęp do aktualnie wybranego wpisu.</string> <string name="EntryChannel_desc">Powiadomienie ułatwiające dostęp do aktualnie wybranego wpisu.</string>
<string name="CloseDbAfterFailedAttempts">Zamknij bazę danych po trzech nieudanych próbach odblokowania biometrycznego.</string> <string name="CloseDbAfterFailedAttempts">Zamknij bazę danych po trzech nieudanych próbach odblokowania biometrycznego.</string>
<string name="WarnFingerprintInvalidated">Uwaga! Uwierzytelnienie biometryczne może zostać unieważnione przez Androida, np. po dodaniu nowego odcisku palca w ustawieniach urządzenia. Upewnij się, że zawsze wiesz, jak odblokować przy użyciu hasła głównego!</string> <string name="WarnFingerprintInvalidated">Uwaga! Uwierzytelnienie biometryczne może zostać unieważnione przez Androida, np. po dodaniu nowego odcisku palca w ustawieniach urządzenia. Upewnij się, że zawsze wiesz, jak odblokować przy użyciu hasła głównego!</string>
<string-array name="ChangeLog_1_12">
<item>Zaktualizowano z Xamarin Android do .net 8</item>
<item>Zaktualizowano Target SDK do 34</item>
<item>Zaktualizowano interfejs użytkownika do Material Design 3</item>
<item>Ulepszono autouzupełnianie, aby działało z aplikacjami opartymi na Compose</item>
<item>Naprawiono dopasowywanie hostów w autouzupełnianiu i wyszukiwaniu</item>
<item>Naprawiono problem związany z generatorem haseł</item>
</string-array>
<string-array name="ChangeLog_1_12_net">
<item>Zaktualizowano SDK OneDrive do wersji 5.68</item>
<item>Zaktualizowano SDK DropBox do wersji 7.0.0</item>
<item>Zaktualizowano Gradle, NewtonsoftJson, FluentFTP, MegaApiClient i okhttp</item>
<item>Naprawiono błąd w wybieraniu plików WebDAV</item>
</string-array>
<string-array name="ChangeLog_1_11"> <string-array name="ChangeLog_1_11">
<item>Dodano pływające przyciski szukania i podglądu TOTP (jeśli są wpisy TOTP).</item> <item>Dodano pływające przyciski szukania i podglądu TOTP (jeśli są wpisy TOTP).</item>
<item>Poprawa wyświetlania pól TOTP na wyraźniejsze i ze wskaźnikiem czasu.</item> <item>Poprawa wyświetlania pól TOTP na wyraźniejsze i ze wskaźnikiem czasu.</item>
<item>TOTP są widoczne teraz w widoku grupy.</item> <item>TOTP są widoczne teraz w widoku grupy.</item>
<item>Skopiuj tekst do schowka po długim naciśnięciu w widoku wpisu.</item> <item>Skopiuj tekst do schowka po długim naciśnięciu w widoku wpisu.</item>
<item>TOTP łatwiej dostępne we wbudowanej klawiaturze.</item> <item>TOTP łatwiej dostępne we wbudowanej klawiaturze.</item>
<item>Show entry notification when autofilling a TOTP entry. This allows to copy the TOTP to clipboard. See preferences to configure the behavior.</item> <item>Pokaż powiadomienie przy automatycznym wypełnianiu wpisu TOTP. Pozwala to na skopiowanie TOTP do schowka. W ustawieniach możesz skonfigurować to zachowanie.</item>
<item>Updated TOTP implementation to resolve compatibility issues with KeePass2 and TrayTOTP</item> <item>Zaktualizowano implementację TOTP, aby rozwiązać problemy związane z kompatybilnością z KeePass2 i TrayTOTP</item>
<item>Małe poprawki</item> <item>Małe poprawki</item>
</string-array> </string-array>
<string-array name="ChangeLog_1_11_net">
<item>Zaktualizuj SDK pCloud, aby zapewnić dostęp do udostępnionych folderów</item>
</string-array>
<string-array name="ChangeLog_1_10"> <string-array name="ChangeLog_1_10">
<item>Dodaj wsparcie dla uprawnień do powiadomień na Androidzie 13+</item> <item>Dodaj wsparcie dla uprawnień do powiadomień na Androidzie 13+</item>
<item>Poprawia wdrażania FTP i SFTP</item> <item>Poprawia wdrażania FTP i SFTP</item>
@@ -809,7 +827,189 @@
* Nowa implementacja dla OneDrive: zawiera wsparcie dla OneDrive for Business, plików współdzielonych, wybranych zakresów dostępu, wielu kont i naprawia problemy z dostępem offline\n * Nowa implementacja dla OneDrive: zawiera wsparcie dla OneDrive for Business, plików współdzielonych, wybranych zakresów dostępu, wielu kont i naprawia problemy z dostępem offline\n
* Poprawki błędów * Poprawki błędów
</string> </string>
<string name="ChangeLog_1_07"> Wersja 1.07\n
* Naprawiono błędy na urządzeniach Samsunga z Androidem 9\n
* Pozwól na otwarcie więcej niż jednej bazy danych, kompatybilnej z KeeAutoExec\n
* SFTP: Możliwość na uwierzytelnianie kluczem publicznym, sprawdzanie zmiany klucza hosta\n
* Wprowadzono wsparcie dla pCloud - dzięki gilbsgilbs!\n
* Jawne wsparcie dla Nextcloud\n
* Ulepszono zapisywanie i aktualizację załączników do wpisów\n
* Więcej opcji dostosowania zachowania do preferencji użytkownika\n
* SSL: Zaufanie certyfikatom użytkownika\n
* Ulepszono autouzupełnianie (działa teraz w FireFox, umożliwia zredukowanie popupów)\n
* Poprawki błędów\n
</string>
<string name="ChangeLog_1_06">Wersja 1.06\n
* Zmieniono aplikację do obsługi Yubikey Challenge-Response z YubiChallenge na ykDroid.\n
* Dodano wsparcie dla Challenge-Response w formacie KeepassXC. Uwaga: Baza danych musi być w formacie KDBX4!\n
* Dodano opcję odrzucania ładowania plików z kosza w Google Drive\n
* Zmieniono implementację TLS dla FTPS, dodano obejście do błędu JSch z serwerami obsługującymi gssapi-with-mic\n
* Poprawki błędów\n
</string>
<string name="ChangeLog_1_05">Wersja 1.05\n
* Użyj kanału powiadomień w Androidzie 8 do konfiguracji, poprzez ustawienia systemu\n
* Ikona wpisu jest pokazywana w powiadomieniu\n
* Użyto adaptacyjnej ikony aplikacji dla Androida 8, używaj okrągłej ikony dla Androida 7\n
* Możliwość aktywnego wyszukiwania po odblokowaniu (zobacz w ustawieniach)\n
* Zmieniono sposób w jaki pliki są zapisywane przez Storage Access Framework, rozwiązano problemy z aktualizowaniem plików na Dysku Google otwieranych przez systemowy menadżer plików\n
* Dodano kilka tekstów informacyjnych, aby uniknąć częstych nieporozumień\n
* Tworzone są lokalne kopie zapasowe pomyślnie otwartych baz danych, aby zmniejszyć ryzyko utraty danych\n
* Zaktualizowano JSch do obsługi nowszych szyfrów SSH\n
* Zezwolono na edycję ustawień połączeń, na przykład kiedy hasło do WebDav zostanie zmienione\n
* Dodano wsparcie dla Yubikey Neo w trybie hasła statycznego\n
* Dodano możliwość wyłączenia sugestii autouzupełniania\n
* Naprawiono wyciek danych do logcat\n
* Poprawki błędów\n
</string>
<string name="ChangeLog_1_04b"> Wersja 1.04b\n
* Naprawienie crasha, gdy użytkownik próbuje włączyć autouzupełnianie na urządzeniach Huawei.\n
</string>
<string name="ChangeLog_1_04">Wersja 1.0.4 \n
* Dodano serwis autouzupełniania dla Androida 8.0 i dalej. \n
* Zaktualizowano biblioteki, narzędzia budownicze oraz wersję Target SDK \n</string>
<string name="ChangeLog_1_03">Wersja 1.03\n
* Usunięto usługę dostępności autouzupełniania, zgodnie z żądaniem Google. Proszę zobaczyć ustawienia dostępu do hasła, gdzie można znaleźć plugin replikujący poprzednią funkcjonalność.\n
* Dodano ponownie aplikacje firm trzecich jako opcję przechowywania plików\n
* Zintegrowano przeglądarkę obrazów do przeglądania zdjęć bez przesyłania ich do innej aplikacji\n
* Uaktualnienie OkHttp rozwiązuje problemy z niektórymi połączeniami\n
* Wsparcie dla wpisów KeeTrayTOTP, teraz obsługuje wpisy Steam\n
</string>
<string name="ChangeLog_1_02">Wersja 1.02\n
* Kilka ulepszeń zabezpieczeń. Dzięki raportowi bezpieczeństwa jean-baptiste.cayrou@thalesgroup.com i vincent.fargues@thalesgroup.com oraz ich współpracy!\n
* Wsparcie dla KeyboardSwapPlugin (zobacz Opcje dostępu do hasła): pozwala przełączać metody wprowadzania automatycznie na urządzeniach bez root-a. Podziękowania dla Mishaal Rahman z XDA-Developers za uczynienie tego możliwym.\n
* Poprawki dl ausługi ułatwień dostępu w ostatnich wersjach Chrome\n
* Naprawiono niepotrzebne czyszczenie danych linii papilarnych\n
* Poprawki drobnych awarii\n
* Zaktualizowano SDK Dropbox do zapewnienia przyszłej kompatybilności\n
* Usunięto raportowanie błędów za pośrednictwem Xamarin Insights\n
* Aktualizacja narzędzi kompilacji\n
</string>
<string name="ChangeLog_1_01g">Wersja 1.01-g\n
* Naprawiono awarię podczas próby pracy offline\n
* Naprawiono nieprawidłowe kodowanie poświadczeń FTP(S)\n
* Naprawiono awarię podczas korzystania z usługi OneDrive na starszych wersjach Androida\n
* Wyświetlanie czasu jako czas lokalny na ekranie wpisu\n
</string>
<string name="ChangeLog_1_01d">Wersja 1.01-d\n
* Poprawiono wyświetlanie listy plików OneDrive\n
* Zezwolono na ignorowanie błędy certyfikatu również kiedy weryfikacji nazwy hosta się nie powiedzie (niezalecane w wersji komercyjnej)\n
* Poprawka do okazjonalnych problemów z szybkim odblokoweaniem pomimo prawidłowego kodu\n </string>
<string name="ChangeLog_0_9_8c">Wersja 0.9.8c\n
* Poprawka luki bezpieczeństwa protokołu SSL w Microsoft Live SDK (używany podczas uzyskiwania dostępu do plików za pośrednictwem OneDrive)\n
* Poprawka błędu: Poprzednia wersja zawierała dwie metody wprowadzania (jedną niedziałającą poprawnie)\n </string>
<string name="ChangeLog_1_01"> Wersja 1.01\n
* dodano wsparcie dla nowego formatu KDBX-4 (kompatybilne z Keepass 2.35) włącznie z generowaniem kluczy Argon2 i szyfrowaniem ChaCha20.\n
* Przepisano przechowywanie plików WebDav, umożliwiając przeglądanie plików z wsparciem nowoczesnego szyfrowania.\n
* Przepisano przechowywanie plików FTP,, umożliwiając przechowywanie plików i z wsparciem szyfrowania (FTPS).\n
* Zaktualizowano SDK OneDrive (poprzednio używane Live SDK nie jest już aktualizowane)\n
* Zaktualizowano SDK Dropbox do wersji 2 (poprzednio używana wersja 1 SDK jest oznaczona jako przestarzała).\n
* Dodano wsparcie dla OwnCloud.\n
* Dodano prośbę o pozwolenie na dostęp do pamięci przed otwieraniem lokalnych plików
</string>
<string name="ChangeLog_1_0_0e"> Wersja 1.0.0e\n
* Naprawiono odblokowywanie odciskiem palca na starszych urządzeniach Samsung z Androidem 6\n
* Dodane natywne wsparcie dla urządzeń x86\n
* Zezwolono na chowanie klawiaturę programowej podczas skanowania odcisku palca\n
* Aktualizacja systemu budowania
</string>
<string name="ChangeLog_1_0_0">Wersja 1.0.0\n
* Odblokowywanie odciskiem palca (Android 6.0+ lub urządzenie Samsung)\n
* Dodano usługę autouzupełniania (Android 5.0+)\n
* Dodano wsparcie dla szablonów wiadomości\n
* Dodano tryb \"pracy offline\"\n
* Umożliwiono kopiowanie wpisów\n
* Tryb autouzupełniania dla nazw pól\n
* Umożliwiono usuwanie wpisów na liście ostatnich plików\n
* Prośba o uprawnienia w trakcie działania aplikacji w Androidzie 6.0\n
* Poprawki błędów (we wbudowanej klawiaturze, podczas wybierania ikon)\n
* Dodano opcję wysyłania raportów o błędach\n
* Dodano wskazówki dotyczące obsługi w kilku miejscach\n
</string>
<string name="ChangeLog_0_9_9"> Wersja 0.9.9\n
* Kompletnie przeprojektowany interfejs użytkownika. Wielkie podziękowania dla Stefano Pignataro (http://www.spstudio.at) za jego pomoc!\n
* Umożliwiono dodawanie własnych ikon\n
* Wsparcie dla trybu Multi Window na urządzeniach Samsung\n
* Zwiększono domyślną liczbę rund szyfrowania dla nowych baz danych\n
* Sprawdzanie czy nie duplikatów kluczy w dodatkowych polach w celu uniknięcia utraty danych\n
</string>
<string name="ChangeLog_0_9_9c"> Wersja 0.9.9c\n
* Powrócił ciemny motyw\n
* Możesz zainstalować inne paczki ikon (ikony w klasycznym stylu Windowsa są dostępne w sklepie Play)\n
* Dodano pytanie o potwierdzenie podczas usuwania elementów z pominięciem kosza\n
* Poprawki błędów (złe wyświetlanie kodowania tajnego klucza OTP, niewłaściwa ikona aplikacji w niektórych miejscach)\n
</string>
<string name="ChangeLog_0_9_8b"> Wersja 0.9.8b\n
* Poprawki błędów (Zapisywanie nie udawało się dla niektórych baz danych, nie działało eksportowanie do lokalnego urządzenia, wybieranie niektórych opcji wysypywało aplikację)\n
</string>
<string name="ChangeLog_0_9_8"> Wersja 0.9.8\n
* Wsparcie dla Storage Access Framework (umożliwia zapis na kartę SD i Google Drive w KP2A Offline)\n
* Próba wykrywania błędnych danych wprowadzanych przez użytkownika podczas wpisywania WebDAV URLs (katalog zamiast pliku)\n
* Zmieniono czcionkę hasła\n
* Umożliwiono zmianę konta Dropbox\n
* Poprawka błędu: Teraz hasło OTP jest zapamiętywane\n</string>
<string name="ChangeLog_0_9_7b"> Wersja 0.9.7b\n
* Zaktualizowano tłumaczenia\n
* Poprawki błędów: brakowało czcionki do haseł w wersji 0.9.7, sortowanie po nazwie nie sortowało grup\n
</string>
<string name="ChangeLog_0_9_7"> Wersja 0.9.7\n
* Dodano wsparcie dla zapisu bazy danych (kdb) dla Keepass 1 (beta!)\n
* Lepsze przełączanie do poprzedniej klawiatury (działa także na nie-zrootowanych urządzeniach)\n
* Wsparcie dla KeeChallenge ze zmienną długością wyzwań\n
* Zabezpieczono przed możliwością zrobienia zrzutów ekranu dla ekranów QuickUnlock i hasła\n
* Odwrócono kolejność sortowania dla Sortuj według daty modyfikacji (teraz malejąco)\n
* Poprawki błędów: Widok notatek jest teraz poprawnie aktualizowany po zmianach. Widoki haseł teraz chowają hasła poprawnie na (miejmy nadzieję) wszystkich urządzeniach; naprawiono błąd, który powodował, że można było dwukrotnie dodać ten sam wpis; naprawiono problem z pokazywaniem ostrzeżenia o zduplikowanych UUID nawet po naprawieniu bazy danych\n
</string>
<string name="ChangeLog_0_9_3_r5"> <b>Wersja 0.9.3 r5</b>\n
* Zawarto poprawki z Xamarin: Keepass2Android jest teraz kompatybilny z ART na Androidzie 4.4.2. Nareszcie!\n
* Poprawki błędów: błędy w synchronizacji (odswieżanie widoku, poprawne sprawdzanie zmian na http), błędy na urządzeniach z Androidem 2.x, błędy w implementacji Google Drive i SkyDrive, czyszczenie schowka przy zamykaniu bazy danych, błąd otwierania załączników, problemy z wyświetlaniem klawiatury\n
</string>
<string name="ChangeLog_0_9_3"><b>Wersja 0.9.3</b>\n
* Nowa klawiatura z wieloma usprawnieniami. Sprawdź ustawienia, aby dostosować.\n
* Wsparcie tylko do odczytu dla kdb (pliki Keepass 1). Eksperymentalne!\n
* Dodano wsparcie protokołu SFTP\n
* Dodano obejście błędu w ART (Android 4.4.2)\n
* Poprawki błędów\n</string>
<string name="ChangeLog_0_9_2"><b>Wersja 0.9.2</b>\n
* Dodano obsługę haseł jednorazowych (kompatybilne z wtyczką OtpKeyProv)\n
* Zintegrowano wparcie NFC dla haseł jednorazowych z YubiKey NEO \n
* Kilka usprawnień interfejsu\n
* Zintegrowano bibliotekę Keepass 2.24\n
* Dodano opcję do zabijania procesu aplikacji (sprawdź ustawienia)\n
* Usprawniona weryfikacja certyfikatów SSL\n
* Poprawki błędów\n</string>
<string name="ChangeLog_0_9_1"><b>Wersja 0.9.1</b>\n
* Dodano integrację ze SkyDrive (Tylko zwyczajna edycja Keepass2Android)\n
* Poprawiono problemy z integracją z Google Drive\n
* Dodano wsparcie dla NTLM
</string>
<string name="ChangeLog_0_8_1"><b>Wersja 0.8.1</b>\n
* KP2A w trybie Offline i \"Online\" może być zainstalowany równocześnie\n
* Dodane nowe tłumaczenia (Dziękujemy wszystkim tłumaczom!)</string>
<string name="ChangeLog_0_8"><b>Wersja 0.8</b>\n
* Ulepszono interfejs użytkownika, szczególnie na urządzeniach z Androidem 4.x\n
* Możliwość używania innych menedżerów plików w celu wyboru plików\n
* Dodano bezpieczniejszy sposób otwierania załączników (z pamięci podręcznej)\n
* Naprawiono błędy podczas \"Edycji\"\n
* Prawdopodobnie wprowadzono nowe błędy :-)</string>
<string name="ChangeLog_keptDonate">Rozszerzone możliwości darowania piwa lub czegoś innego</string> <string name="ChangeLog_keptDonate">Rozszerzone możliwości darowania piwa lub czegoś innego</string>
<string name="ChangeLog_0_7"> <b>Wersja 0.7</b> \n * Zwiększono szybkość ładowania: transformacje kluczy teraz 10 razy szybsze\n
* Dodano wirtualną klawiaturę Keepass2Android: przełącz się na nią podczas wprowadzania danych logowania. Chroni przed podsłuchiwaniem haseł z użyciem schowka (wyłącz stare powiadomienia o schowku w ustawieniach).
* Dodano opcję postawienia mi piwa lub czegoś innego (zobacz w menu)
</string>
<string name="ChangeLog"> <b>Wersja 0.6.2</b>\n
* Integracja Google Drive/Dropbox/(...): Otworzenie pliku .kdbx w Google Drive lub Dropbox uruchomi teraz KP2A.\n
* Poprawiono okno wyszukiwania \n
* Ulepszono wyniki wyszukiwania dla udostępnionych adresów URL z subdomenami\n
* Dodano opcje przesyłania opinii, oceniania i tłumaczenia aplikacji w menu\n
\n
<b>Wersja 0.6.1</b>\n
* Wykrywanie zmian w bazie danych w tle (np. spowodowanych działaniem aplikacji synchronizującej)\n
* Ulepszone wyszukiwanie adresów URL z przeglądarki\n
* Potwierdzenie przed porzuceniem zmian\n
\n
<b>Wersja 0.6</b>\n
Pierwsze publiczne wydanie
</string>
<string-array name="clipboard_timeout_options"> <string-array name="clipboard_timeout_options">
<item>30 sekund</item> <item>30 sekund</item>
<item>1 minuta</item> <item>1 minuta</item>
@@ -900,5 +1100,9 @@
<string name="AutofillWarning_Intro">Zamierzasz wstawić poświadczenia dla domeny \"%1$s\" do aplikacji \"%2$s\".</string> <string name="AutofillWarning_Intro">Zamierzasz wstawić poświadczenia dla domeny \"%1$s\" do aplikacji \"%2$s\".</string>
<string name="AutofillWarning_FillDomainInUntrustedApp">Jeśli ufasz \"%2$s\" należącym do \"%1$s\" lub ufasz aplikacji \"%2$s\", aby nie używać danych logowania (np. ponieważ jest to zaufana aplikacja przeglądarki), jest w porządku, aby kontynuować. Jeśli nie, proszę anulować.</string> <string name="AutofillWarning_FillDomainInUntrustedApp">Jeśli ufasz \"%2$s\" należącym do \"%1$s\" lub ufasz aplikacji \"%2$s\", aby nie używać danych logowania (np. ponieważ jest to zaufana aplikacja przeglądarki), jest w porządku, aby kontynuować. Jeśli nie, proszę anulować.</string>
<string name="AutofillWarning_trustAsBrowser">Akceptuj zawsze w \"%1$s\"</string> <string name="AutofillWarning_trustAsBrowser">Akceptuj zawsze w \"%1$s\"</string>
<string name="kp2a_switch_on_sendgodone">Przełącz klawiaturę po zakończeniu</string>
<string name="kp2a_switch_on_sendgodone_summary">Przełącz po naciśnięciu wyślij/idź/wykonaj</string>
<string name="qr_scanning_error_no_google_play_services">Skanowanie kodów QR wymaga usług Google Play. Zainstaluj lub zaktualizuj usługi Google Play na swoim urządzeniu.</string>
<string name="english_ime_settings">Ustawienia klawiatury Keepass2Android</string> <string name="english_ime_settings">Ustawienia klawiatury Keepass2Android</string>
<string name="switch_keyboard_inside_kp2a_enabled">Uwaga: Włączono w ustawieniach Aplikacja -> Bezpieczeństwo, opcję „Używaj wbudowanej klawiatury w Keepass2Android”. Może to powodować pojawianie się tego okna przy otwieraniu aplikacji lub edycji wpisu.</string>
</resources> </resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="about_feedback">反馈</string> <string name="about_feedback">反馈</string>
<string name="AboutText">Keepass2Android 是一款支持 Keepass 2.x 数据库读写的密码管理应用。</string> <string name="AboutText">Keepass2Android 是一款支持 Keepass 2.x 数据库读写的Android平台的密码管理应用。</string>
<string name="CreditsText">用户界面基于 Brian Pellin 开发的 KeepassDroid 项目。数据库操作代码基于 Dominik Reichl 开发的 KeePass 项目。Android 机器人图案根据 Google 创建和共享的作品进行复制或修改,并依照知识共享署名 3.0 协议进行使用。</string> <string name="CreditsText">用户界面基于 Brian Pellin 开发的 KeepassDroid 项目。数据库操作代码基于 Dominik Reichl 开发的 KeePass 项目。Android 机器人图案根据 Google 创建和共享的作品进行复制或修改,并依照知识共享署名 3.0 协议进行使用。</string>
<string name="CreditsTextSFTP">SFTP 支持由 JCraft, Inc. 创建的 JSch 库(基于 BSD 许可证)实现</string> <string name="CreditsTextSFTP">SFTP 支持由 JCraft, Inc. 创建的 JSch 库(基于 BSD 许可证)实现</string>
<string name="CreditsIcons">锤子图标是由 John Caserta 通过 Noun Project 创建的。企鹅图标是由 Adriano Emerick 通过 Noun Project 创建的。羽毛图标是由 Jon Testa 通过 Noun Project 创建的。苹果图标是由 Ava Rowell 通过 Noun Project 创建的。图片图标来自 https://icons8.com/icon/5570/Picture 。</string> <string name="CreditsIcons">锤子图标是由 John Caserta 通过 Noun Project 创建的。企鹅图标是由 Adriano Emerick 通过 Noun Project 创建的。羽毛图标是由 Jon Testa 通过 Noun Project 创建的。苹果图标是由 Ava Rowell 通过 Noun Project 创建的。图片图标来自 https://icons8.com/icon/5570/Picture 。</string>
@@ -725,9 +725,15 @@
<item></item> <item></item>
<item>Upgraded to Material 3 user interface</item> <item>Upgraded to Material 3 user interface</item>
<item>Improve autofill to work with Compose apps</item> <item>Improve autofill to work with Compose apps</item>
<item>Fix hostname matching in autofill and search</item> <item>修复在自动填充和搜索时主机名的匹配问题</item>
<item>Fix issue with password generator</item> <item>Fix issue with password generator</item>
</string-array> </string-array>
<string-array name="ChangeLog_1_12_net">
<item>Upgraded OneDrive SDK to version 5.68</item>
<item>Upgraded Dropbox SDK to version 7.0.0</item>
<item>Upgraded Gradle, NewtonsoftJson, FluentFTP, MegaApiClient and okhttp</item>
<item>修复WebDav中文件选择的错误</item>
</string-array>
<string-array name="ChangeLog_1_11"> <string-array name="ChangeLog_1_11">
<item>添加了用于搜索和概览 TOTP 的浮动按钮如果存在TOTP条目</item> <item>添加了用于搜索和概览 TOTP 的浮动按钮如果存在TOTP条目</item>
<item>通过添加超时指示器并突出显示,改进 TOTP 字段的显示效果</item> <item>通过添加超时指示器并突出显示,改进 TOTP 字段的显示效果</item>

View File

@@ -93,6 +93,8 @@
<string name="disable_fingerprint_unlock">Disable Biometric Unlock</string> <string name="disable_fingerprint_unlock">Disable Biometric Unlock</string>
<string name="enable_fingerprint_unlock">Enable full Biometric Unlock</string> <string name="enable_fingerprint_unlock">Enable full Biometric Unlock</string>
<string name="enable_fingerprint_quickunlock">Enable Biometric Unlock for QuickUnlock</string> <string name="enable_fingerprint_quickunlock">Enable Biometric Unlock for QuickUnlock</string>
<string name="password_based_quick_unlock_not_available">Password-based QuickUnlock not available</string>
<string name="password_based_quick_unlock_not_available_text">QuickUnlock using a part of your password is blocked because screen lock is not activated on your device. This behavior is to protect you in case somebody watched you entering your QuickUnlock key.</string>
<string name="fingerprint_unlock_failed">Biometric Unlock failed. Decryption key was invalidated by Android OS. This usually happens if a biometric authentication or security settings were changed. </string> <string name="fingerprint_unlock_failed">Biometric Unlock failed. Decryption key was invalidated by Android OS. This usually happens if a biometric authentication or security settings were changed. </string>
<string name="fingerprint_disabled_wrong_masterkey">Unlocking the database failed: Invalid composite key. Biometric Unlock was disabled because apparently the stored master password is no longer valid. </string> <string name="fingerprint_disabled_wrong_masterkey">Unlocking the database failed: Invalid composite key. Biometric Unlock was disabled because apparently the stored master password is no longer valid. </string>
<string name="fingerprint_reenable">Please re-enable Biometric Unlock for the new master password.</string> <string name="fingerprint_reenable">Please re-enable Biometric Unlock for the new master password.</string>
@@ -319,6 +321,7 @@
<string name="QuickUnlock_label_secure">Enter QuickUnlock code:</string> <string name="QuickUnlock_label_secure">Enter QuickUnlock code:</string>
<string name="QuickUnlock_button">QuickUnlock!</string> <string name="QuickUnlock_button">QuickUnlock!</string>
<string name="QuickUnlock_lockButton">Close database</string> <string name="QuickUnlock_lockButton">Close database</string>
<string name="enable_screen_lock">Enable screen lock</string>
<string name="QuickUnlockDefaultEnabled_title">Enable QuickUnlock by default</string> <string name="QuickUnlockDefaultEnabled_title">Enable QuickUnlock by default</string>
<string name="QuickUnlockDefaultEnabled_summary">Defines whether QuickUnlock is enabled by default or not.</string> <string name="QuickUnlockDefaultEnabled_summary">Defines whether QuickUnlock is enabled by default or not.</string>
<string name="ViewDatabaseSecure_title">Protect database display</string> <string name="ViewDatabaseSecure_title">Protect database display</string>
@@ -727,6 +730,13 @@
<string name="CloseDbAfterFailedAttempts">Close database after three failed biometric unlock attempts.</string> <string name="CloseDbAfterFailedAttempts">Close database after three failed biometric unlock attempts.</string>
<string name="WarnFingerprintInvalidated">Warning! Biometric authentication can be invalidated by Android, e.g. after adding a new fingerprint in your device settings. Make sure you always know how to unlock with your master password!</string> <string name="WarnFingerprintInvalidated">Warning! Biometric authentication can be invalidated by Android, e.g. after adding a new fingerprint in your device settings. Make sure you always know how to unlock with your master password!</string>
<string-array name="ChangeLog_1_13">
<item>Improved password quality estimation by considering most popular passwords.</item>
<item>Block password-based QuickUnlock (for security reasons) if the device does not have a screen lock activated.</item>
<item>Update network security configuration to disable clear-text transfer.</item>
</string-array>
<string-array name="ChangeLog_1_12"> <string-array name="ChangeLog_1_12">
<item>Upgraded from Xamarin Android to .net 8</item> <item>Upgraded from Xamarin Android to .net 8</item>
<item>Upgraded to Target SDK 34</item> <item>Upgraded to Target SDK 34</item>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config> <network-security-config>
<base-config cleartextTrafficPermitted="true"> <base-config>
<trust-anchors> <trust-anchors>
<certificates src="system" /> <certificates src="system" />
<certificates src="user" /> <certificates src="user" />

View File

@@ -351,6 +351,9 @@ namespace keepass2android
QuickUnlockEnabled = enabled; QuickUnlockEnabled = enabled;
} }
public bool ScreenLockWasEnabledWhenOpeningDatabase { get; set; }
public bool QuickUnlockEnabled { get; private set; } public bool QuickUnlockEnabled { get; private set; }
public int QuickUnlockKeyLength { get; private set; } public int QuickUnlockKeyLength { get; private set; }