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
@@ -29,197 +29,226 @@ using KeePassLib.Utility;
namespace KeePassLib.Cryptography namespace KeePassLib.Cryptography
{ {
/// <summary> /// <summary>
/// Algorithms supported by <c>CryptoRandomStream</c>. /// Algorithms supported by <c>CryptoRandomStream</c>.
/// </summary> /// </summary>
public enum CrsAlgorithm public enum CrsAlgorithm
{ {
/// <summary> /// <summary>
/// Not supported. /// Not supported.
/// </summary> /// </summary>
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,
/// <summary> /// <summary>
/// Salsa20 stream cipher algorithm. /// Salsa20 stream cipher algorithm.
/// </summary> /// </summary>
Salsa20 = 2, Salsa20 = 2,
/// <summary> /// <summary>
/// ChaCha20 stream cipher algorithm. /// ChaCha20 stream cipher algorithm.
/// </summary> /// </summary>
ChaCha20 = 3, ChaCha20 = 3,
Count = 4 Count = 4
} }
/// <summary> /// <summary>
/// A random stream class. The class is initialized using random /// A random stream class. The class is initialized using random
/// bytes provided by the caller. The produced stream has random /// bytes provided by the caller. The produced stream has random
/// properties, but for the same seed always the same stream /// properties, but for the same seed always the same stream
/// is produced, i.e. this class can be used as stream cipher. /// is produced, i.e. this class can be used as stream cipher.
/// </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 byte m_i = 0; private readonly byte[] m_pbIV = null;
private byte m_j = 0;
private Salsa20Cipher m_salsa20 = null; private readonly ChaCha20Cipher m_chacha20 = null;
private ChaCha20Cipher m_chacha20 = null; private readonly Salsa20Cipher m_salsa20 = null;
/// <summary> private readonly byte[] m_pbState = null;
/// Construct a new cryptographically secure random stream object. private byte m_i = 0;
/// </summary> private byte m_j = 0;
/// <param name="genAlgorithm">Algorithm to use.</param>
/// <param name="pbKey">Initialization key. Must not be <c>null</c> and
/// must contain at least 1 byte.</param>
public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey)
{
if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); }
/// <exception cref="System.ArgumentNullException">Thrown if the
int cbKey = pbKey.Length;
if(cbKey <= 0)
{
Debug.Assert(false); // Need at least one byte
throw new ArgumentOutOfRangeException("pbKey");
}
/// <paramref name="pbKey" /> parameter is <c>null</c>.</exception>
m_crsAlgorithm = a;
/// <exception cref="System.ArgumentException">Thrown if the
if(a == CrsAlgorithm.ChaCha20)
{
byte[] pbKey32 = new byte[32];
byte[] pbIV12 = new byte[12];
/// <paramref name="pbKey" /> parameter contains no bytes or the
using(SHA512Managed h = new SHA512Managed())
{
byte[] pbHash = h.ComputeHash(pbKey);
Array.Copy(pbHash, pbKey32, 32);
Array.Copy(pbHash, 32, pbIV12, 0, 12);
MemUtil.ZeroByteArray(pbHash);
}
/// algorithm is unknown.</exception>
m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true);
}
else if(a == CrsAlgorithm.Salsa20)
{
byte[] pbKey32 = CryptoUtil.HashSha256(pbKey);
byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
0x97, 0x20, 0x5D, 0x2A }; // Unique constant
m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8); /// <summary>
} /// Construct a new cryptographically secure random stream object.
else if(a == CrsAlgorithm.ArcFourVariant) /// </summary>
{ /// <param name="a">Algorithm to use.</param>
// Fill the state linearly /// <param name="pbKey">Initialization key. Must not be <c>null</c>
m_pbState = new byte[256]; /// and must contain at least 1 byte.</param>
for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w; public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey)
{
if (pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); }
unchecked int cbKey = pbKey.Length;
{ if (cbKey <= 0)
byte j = 0, t; {
int inxKey = 0; Debug.Assert(false); // Need at least one byte
for(int w = 0; w < 256; ++w) // Key setup throw new ArgumentOutOfRangeException("pbKey");
{ }
j += (byte)(m_pbState[w] + pbKey[inxKey]);
t = m_pbState[0]; // Swap entries m_alg = a;
m_pbState[0] = m_pbState[j];
m_pbState[j] = t;
++inxKey; if (a == CrsAlgorithm.ChaCha20)
if(inxKey >= cbKey) inxKey = 0; {
} m_pbKey = new byte[32];
} m_pbIV = new byte[12];
GetRandomBytes(512); // Increases security, see cryptanalysis using (SHA512Managed h = new SHA512Managed())
} {
else // Unknown algorithm byte[] pbHash = h.ComputeHash(pbKey);
{ Array.Copy(pbHash, m_pbKey, 32);
Debug.Assert(false); Array.Copy(pbHash, 32, m_pbIV, 0, 12);
throw new ArgumentOutOfRangeException("a"); MemUtil.ZeroByteArray(pbHash);
} }
}
public void Dispose() m_chacha20 = new ChaCha20Cipher(m_pbKey, m_pbIV, true);
{ }
Dispose(true); else if (a == CrsAlgorithm.Salsa20)
GC.SuppressFinalize(this); {
} m_pbKey = CryptoUtil.HashSha256(pbKey);
m_pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
0x97, 0x20, 0x5D, 0x2A }; // Unique constant
private void Dispose(bool disposing) m_salsa20 = new Salsa20Cipher(m_pbKey, m_pbIV);
{ }
if(disposing) else if (a == CrsAlgorithm.ArcFourVariant)
{ {
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) // Fill the state linearly
m_chacha20.Dispose(); m_pbState = new byte[256];
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) for (int w = 0; w < 256; ++w) m_pbState[w] = (byte)w;
m_salsa20.Dispose();
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant)
{
MemUtil.ZeroByteArray(m_pbState);
m_i = 0;
m_j = 0;
}
else { Debug.Assert(false); }
}
}
/// <summary> unchecked
/// Get <paramref name="uRequestedCount" /> random bytes. {
/// </summary> byte j = 0, t;
/// <param name="uRequestedCount">Number of random bytes to retrieve.</param> int inxKey = 0;
/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns> for (int w = 0; w < 256; ++w) // Key setup
public byte[] GetRandomBytes(uint uRequestedCount) {
{ j += (byte)(m_pbState[w] + pbKey[inxKey]);
if(uRequestedCount == 0) return MemUtil.EmptyByteArray;
if(uRequestedCount > (uint)int.MaxValue) t = m_pbState[0]; // Swap entries
throw new ArgumentOutOfRangeException("uRequestedCount"); m_pbState[0] = m_pbState[j];
int cb = (int)uRequestedCount; m_pbState[j] = t;
byte[] pbRet = new byte[cb]; ++inxKey;
if (inxKey >= cbKey) inxKey = 0;
}
}
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) GetRandomBytes(512); // Increases security, see cryptanalysis
m_chacha20.Encrypt(pbRet, 0, cb); }
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) else // Unknown algorithm
m_salsa20.Encrypt(pbRet, 0, cb); {
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) Debug.Assert(false);
{ throw new ArgumentOutOfRangeException("a");
unchecked }
{ }
for(int w = 0; w < cb; ++w)
{
++m_i;
m_j += m_pbState[m_i];
byte t = m_pbState[m_i]; // Swap entries public void Dispose()
m_pbState[m_i] = m_pbState[m_j]; {
m_pbState[m_j] = t; Dispose(true);
GC.SuppressFinalize(this);
}
t = (byte)(m_pbState[m_i] + m_pbState[m_j]); private void Dispose(bool disposing)
pbRet[w] = m_pbState[t]; {
} if (disposing)
} {
} if (m_alg == CrsAlgorithm.ChaCha20)
else { Debug.Assert(false); } m_chacha20.Dispose();
else if (m_alg == CrsAlgorithm.Salsa20)
m_salsa20.Dispose();
else if (m_alg == CrsAlgorithm.ArcFourVariant)
{
MemUtil.ZeroByteArray(m_pbState);
m_i = 0;
m_j = 0;
}
else { Debug.Assert(false); }
return pbRet; if (m_pbKey != null) MemUtil.ZeroByteArray(m_pbKey);
} if (m_pbIV != null) MemUtil.ZeroByteArray(m_pbIV);
public ulong GetRandomUInt64() m_bDisposed = true;
{ }
byte[] pb = GetRandomBytes(8); }
return MemUtil.BytesToUInt64(pb);
} /// <summary>
/// Get <paramref name="uRequestedCount" /> random bytes.
/// </summary>
/// <param name="uRequestedCount">Number of random bytes to retrieve.</param>
/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns>
public byte[] GetRandomBytes(uint uRequestedCount)
{
if (m_bDisposed) throw new ObjectDisposedException(null);
if (uRequestedCount == 0) return MemUtil.EmptyByteArray;
if (uRequestedCount > (uint)int.MaxValue)
throw new ArgumentOutOfRangeException("uRequestedCount");
int cb = (int)uRequestedCount;
byte[] pbRet = new byte[cb];
if (m_alg == CrsAlgorithm.ChaCha20)
m_chacha20.Encrypt(pbRet, 0, cb);
else if (m_alg == CrsAlgorithm.Salsa20)
m_salsa20.Encrypt(pbRet, 0, cb);
else if (m_alg == CrsAlgorithm.ArcFourVariant)
{
unchecked
{
for (int w = 0; w < cb; ++w)
{
++m_i;
m_j += m_pbState[m_i];
byte t = m_pbState[m_i]; // Swap entries
m_pbState[m_i] = m_pbState[m_j];
m_pbState[m_j] = t;
t = (byte)(m_pbState[m_i] + m_pbState[m_j]);
pbRet[w] = m_pbState[t];
}
}
}
else { Debug.Assert(false); }
return pbRet;
}
public ulong GetRandomUInt64()
{
byte[] pb = GetRandomBytes(8);
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,22 +266,21 @@ 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,333 +19,311 @@
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; /// <summary>
/// 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";
private const int CharTabSize = (0x10000 / 8); // internal static readonly string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
private List<char> m_vChars = new List<char>(); [Obsolete]
private byte[] m_vTab = new byte[CharTabSize]; public static string SpecialChars { get { return PwCharSet.Special; } }
[Obsolete]
public static string HighAnsiChars { get { return PwCharSet.Latin1S; } }
private static string m_strHighAnsi = null; private readonly List<char> m_lChars = new List<char>();
public static string HighAnsiChars private readonly byte[] m_vTab = new byte[0x10000 / 8];
{
get
{
if(m_strHighAnsi == null) { new PwCharSet(); } // Create string
Debug.Assert(m_strHighAnsi != null);
return m_strHighAnsi;
}
}
private static string m_strSpecial = null; /// <summary>
public static string SpecialChars /// Create a new, empty character set.
{ /// </summary>
get public PwCharSet()
{ {
if(m_strSpecial == null) { new PwCharSet(); } // Create string Debug.Assert(PwCharSet.Latin1S.Length == (16 * 6 - 2));
Debug.Assert(m_strSpecial != null); }
return m_strSpecial;
}
}
/// <summary> public PwCharSet(string strCharSet)
/// Create a new, empty character set collection object. {
/// </summary> Add(strCharSet);
public PwCharSet() }
{
Initialize(true);
}
public PwCharSet(string strCharSet) /// <summary>
{ /// Number of characters in this set.
Initialize(true); /// </summary>
Add(strCharSet); public uint Size
} {
get { return (uint)m_lChars.Count; }
}
private PwCharSet(bool bFullInitialize) /// <summary>
{ /// Get a character of the set using an index.
Initialize(bFullInitialize); /// </summary>
} /// <param name="uPos">Index of the character to get.</param>
/// <returns>Character at the specified position. If the index is invalid,
/// an <c>ArgumentOutOfRangeException</c> is thrown.</returns>
public char this[uint uPos]
{
get
{
if (uPos >= (uint)m_lChars.Count)
throw new ArgumentOutOfRangeException("uPos");
private void Initialize(bool bFullInitialize) return m_lChars[(int)uPos];
{ }
Clear(); }
if(!bFullInitialize) return; public bool Equals(PwCharSet other)
{
if (object.ReferenceEquals(other, this)) return true;
if (object.ReferenceEquals(other, null)) return false;
if(m_strHighAnsi == null) if (m_lChars.Count != other.m_lChars.Count) return false;
{
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(); return MemUtil.ArraysEqual(m_vTab, other.m_vTab);
} }
if(m_strSpecial == null) public override bool Equals(object obj)
{ {
PwCharSet pcs = new PwCharSet(false); return Equals(obj as PwCharSet);
pcs.AddRange('!', '/'); }
pcs.AddRange(':', '@');
pcs.AddRange('[', '`');
pcs.Add(@"|~");
pcs.Remove(@"-_ ");
pcs.Remove(PwCharSet.Brackets);
m_strSpecial = pcs.ToString(); public override int GetHashCode()
} {
} return (int)MemUtil.Hash32(m_vTab, 0, m_vTab.Length);
}
/// <summary> /// <summary>
/// Number of characters in this set. /// Remove all characters from this set.
/// </summary> /// </summary>
public uint Size public void Clear()
{ {
get { return (uint)m_vChars.Count; } m_lChars.Clear();
} Array.Clear(m_vTab, 0, m_vTab.Length);
}
/// <summary> public bool Contains(char ch)
/// Get a character of the set using an index. {
/// </summary> return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue);
/// <param name="uPos">Index of the character to get.</param> }
/// <returns>Character at the specified position. If the index is invalid,
/// an <c>ArgumentOutOfRangeException</c> is thrown.</returns>
public char this[uint uPos]
{
get
{
if(uPos >= (uint)m_vChars.Count)
throw new ArgumentOutOfRangeException("uPos");
return m_vChars[(int)uPos]; public bool Contains(string strCharacters)
} {
} Debug.Assert(strCharacters != null);
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
/// <summary> foreach (char ch in strCharacters)
/// Remove all characters from this set. {
/// </summary> if (!Contains(ch)) return false;
public void Clear() }
{
m_vChars.Clear();
Array.Clear(m_vTab, 0, m_vTab.Length);
}
public bool Contains(char ch) return true;
{ }
return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue);
}
public bool Contains(string strCharacters) /// <summary>
{ /// Add characters to the set.
Debug.Assert(strCharacters != null); /// </summary>
if(strCharacters == null) throw new ArgumentNullException("strCharacters"); /// <param name="ch">Character to add.</param>
public void Add(char ch)
{
if (ch == char.MinValue) { Debug.Assert(false); return; }
foreach(char ch in strCharacters) if (!Contains(ch))
{ {
if(!Contains(ch)) return false; m_lChars.Add(ch);
} m_vTab[ch / 8] |= (byte)(1 << (ch % 8));
}
}
return true; /// <summary>
} /// Add characters to the set.
/// </summary>
/// <param name="strCharSet">String containing characters to add.</param>
public void Add(string strCharSet)
{
Debug.Assert(strCharSet != null);
if (strCharSet == null) throw new ArgumentNullException("strCharSet");
/// <summary> foreach (char ch in strCharSet)
/// Add characters to the set. Add(ch);
/// </summary> }
/// <param name="ch">Character to add.</param>
public void Add(char ch)
{
if(ch == char.MinValue) { Debug.Assert(false); return; }
if(!Contains(ch)) public void Add(string strCharSet1, string strCharSet2)
{ {
m_vChars.Add(ch); Add(strCharSet1);
m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); Add(strCharSet2);
} }
}
/// <summary> public void Add(string strCharSet1, string strCharSet2, string strCharSet3)
/// Add characters to the set. {
/// </summary> Add(strCharSet1);
/// <param name="strCharSet">String containing characters to add.</param> Add(strCharSet2);
public void Add(string strCharSet) Add(strCharSet3);
{ }
Debug.Assert(strCharSet != null);
if(strCharSet == null) throw new ArgumentNullException("strCharSet");
m_vChars.Capacity = m_vChars.Count + strCharSet.Length; public void AddRange(char chMin, char chMax)
{
for (char ch = chMin; ch < chMax; ++ch)
Add(ch);
foreach(char ch in strCharSet) Add(chMax);
Add(ch); }
}
public void Add(string strCharSet1, string strCharSet2) public bool AddCharSet(char chCharSetIdentifier)
{ {
Add(strCharSet1); bool bResult = true;
Add(strCharSet2);
}
public void Add(string strCharSet1, string strCharSet2, string strCharSet3) switch (chCharSetIdentifier)
{ {
Add(strCharSet1); case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break;
Add(strCharSet2); case 'A':
Add(strCharSet3); Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
} PwCharSet.Digits); break;
case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break;
case 'c': Add(PwCharSet.LowerConsonants); break;
case 'C':
Add(PwCharSet.LowerConsonants,
PwCharSet.UpperConsonants); break;
case 'z': Add(PwCharSet.UpperConsonants); break;
case 'd': Add(PwCharSet.Digits); break; // Digit
case 'h': Add(PwCharSet.LowerHex); break;
case 'H': Add(PwCharSet.UpperHex); break;
case 'l': Add(PwCharSet.LowerCase); break;
case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break;
case 'u': Add(PwCharSet.UpperCase); break;
case 'p': Add(PwCharSet.Punctuation); break;
case 'b': Add(PwCharSet.Brackets); break;
case 's': Add(PwCharSet.PrintableAsciiSpecial); break;
case 'S':
Add(PwCharSet.UpperCase, PwCharSet.LowerCase);
Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break;
case 'v': Add(PwCharSet.LowerVowels); break;
case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break;
case 'Z': Add(PwCharSet.UpperVowels); break;
case 'x': Add(PwCharSet.Latin1S); break;
default: bResult = false; break;
}
public void AddRange(char chMin, char chMax) return bResult;
{ }
m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1;
for(char ch = chMin; ch < chMax; ++ch) public bool Remove(char ch)
Add(ch); {
m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8)));
return m_lChars.Remove(ch);
}
Add(chMax); public bool Remove(string strCharacters)
} {
Debug.Assert(strCharacters != null);
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
public bool AddCharSet(char chCharSetIdentifier) bool bResult = true;
{ foreach (char ch in strCharacters)
bool bResult = true; {
if (!Remove(ch)) bResult = false;
}
switch(chCharSetIdentifier) return bResult;
{ }
case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break;
case 'A': Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
PwCharSet.Digits); break;
case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break;
case 'c': Add(PwCharSet.LowerConsonants); break;
case 'C': Add(PwCharSet.LowerConsonants,
PwCharSet.UpperConsonants); break;
case 'z': Add(PwCharSet.UpperConsonants); break;
case 'd': Add(PwCharSet.Digits); break; // Digit
case 'h': Add(PwCharSet.LowerHex); break;
case 'H': Add(PwCharSet.UpperHex); break;
case 'l': Add(PwCharSet.LowerCase); break;
case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break;
case 'u': Add(PwCharSet.UpperCase); break;
case 'p': Add(PwCharSet.Punctuation); break;
case 'b': Add(PwCharSet.Brackets); break;
case 's': Add(PwCharSet.PrintableAsciiSpecial); break;
case 'S': Add(PwCharSet.UpperCase, PwCharSet.LowerCase);
Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break;
case 'v': Add(PwCharSet.LowerVowels); break;
case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break;
case 'Z': Add(PwCharSet.UpperVowels); break;
case 'x': Add(m_strHighAnsi); break;
default: bResult = false; break;
}
return bResult; public bool RemoveIfAllExist(string strCharacters)
} {
Debug.Assert(strCharacters != null);
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
public bool Remove(char ch) if (!Contains(strCharacters))
{ return false;
m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8)));
return m_vChars.Remove(ch);
}
public bool Remove(string strCharacters) return Remove(strCharacters);
{ }
Debug.Assert(strCharacters != null);
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
bool bResult = true; /// <summary>
foreach(char ch in strCharacters) /// Convert the character set to a string containing all its characters.
{ /// </summary>
if(!Remove(ch)) bResult = false; /// <returns>String containing all character set characters.</returns>
} public override string ToString()
{
StringBuilder sb = new StringBuilder(m_lChars.Count);
foreach (char ch in m_lChars)
sb.Append(ch);
return bResult; return sb.ToString();
} }
public bool RemoveIfAllExist(string strCharacters) public string PackAndRemoveCharRanges()
{ {
Debug.Assert(strCharacters != null); StringBuilder sb = new StringBuilder();
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
if(!Contains(strCharacters)) sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_');
return false; sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Special) ? 'S' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_');
sb.Append(RemoveIfAllExist("-") ? 'm' : '_');
sb.Append(RemoveIfAllExist("_") ? 'u' : '_');
sb.Append(RemoveIfAllExist(" ") ? 's' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Latin1S) ? 'H' : '_');
return Remove(strCharacters); return sb.ToString();
} }
/// <summary> public void UnpackCharRanges(string strRanges)
/// Convert the character set to a string containing all its characters. {
/// </summary> if (strRanges == null) { Debug.Assert(false); return; }
/// <returns>String containing all character set characters.</returns> if (strRanges.Length < 10) { Debug.Assert(false); return; }
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach(char ch in m_vChars)
sb.Append(ch);
return sb.ToString(); if (strRanges[0] != '_') Add(PwCharSet.UpperCase);
} if (strRanges[1] != '_') Add(PwCharSet.LowerCase);
if (strRanges[2] != '_') Add(PwCharSet.Digits);
public string PackAndRemoveCharRanges() if (strRanges[3] != '_') Add(PwCharSet.Special);
{ if (strRanges[4] != '_') Add(PwCharSet.Punctuation);
StringBuilder sb = new StringBuilder(); if (strRanges[5] != '_') Add('-');
if (strRanges[6] != '_') Add('_');
sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); if (strRanges[7] != '_') Add(' ');
sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); if (strRanges[8] != '_') Add(PwCharSet.Brackets);
sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); if (strRanges[9] != '_') Add(PwCharSet.Latin1S);
sb.Append(RemoveIfAllExist(m_strSpecial) ? 'S' : '_'); }
sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_'); }
sb.Append(RemoveIfAllExist(@"-") ? 'm' : '_');
sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_');
sb.Append(RemoveIfAllExist(@" ") ? 's' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_');
sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_');
return sb.ToString();
}
public void UnpackCharRanges(string strRanges)
{
if(strRanges == null) { Debug.Assert(false); return; }
if(strRanges.Length < 10) { Debug.Assert(false); return; }
if(strRanges[0] != '_') Add(PwCharSet.UpperCase);
if(strRanges[1] != '_') Add(PwCharSet.LowerCase);
if(strRanges[2] != '_') Add(PwCharSet.Digits);
if(strRanges[3] != '_') Add(m_strSpecial);
if(strRanges[4] != '_') Add(PwCharSet.Punctuation);
if(strRanges[5] != '_') Add('-');
if(strRanges[6] != '_') Add('_');
if(strRanges[7] != '_') Add(' ');
if(strRanges[8] != '_') Add(PwCharSet.Brackets);
if(strRanges[9] != '_') Add(m_strHighAnsi);
}
}
} }

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,133 +20,172 @@
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;
namespace KeePassLib.Cryptography.PasswordGenerator namespace KeePassLib.Cryptography.PasswordGenerator
{ {
public enum PwgError public enum PwgError
{ {
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,
CustomPwGeneratorPool pwAlgorithmPool)
{
Debug.Assert(pwProfile != null);
if (pwProfile == null) throw new ArgumentNullException("pwProfile");
CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy); private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy,
PwgError e = PwgError.Unknown; out byte[] pbKey)
{
pbKey = CryptoRandom.Instance.GetRandomBytes(128);
if (pwProfile.GeneratorType == PasswordGeneratorType.CharSet) // Mix in additional entropy
e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs); Debug.Assert(pbKey.Length >= 64);
else if (pwProfile.GeneratorType == PasswordGeneratorType.Pattern) if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length != 0))
e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs); {
else if (pwProfile.GeneratorType == PasswordGeneratorType.Custom) using (SHA512Managed h = new SHA512Managed())
e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool); {
else { Debug.Assert(false); psOut = ProtectedString.Empty; } byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
MemUtil.ZeroByteArray(pbHash);
}
}
return e; return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey);
} }
private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) internal static char GenerateCharacter(PwCharSet pwCharSet,
{ CryptoRandomStream crsRandomSource)
byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128); {
uint cc = pwCharSet.Size;
if (cc == 0) return char.MinValue;
// Mix in additional entropy uint i = (uint)crsRandomSource.GetRandomUInt64(cc);
Debug.Assert(pbKey.Length >= 64); return pwCharSet[i];
if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) }
{
using (SHA512Managed h = new SHA512Managed())
{
byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
}
}
return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
} {
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;
}
internal static char GenerateCharacter(PwProfile pwProfile, if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike);
PwCharSet pwCharSet, CryptoRandomStream crsRandomSource)
{
if (pwCharSet.Size == 0) return char.MinValue;
ulong uIndex = crsRandomSource.GetRandomUInt64(); if (!string.IsNullOrEmpty(pwProfile.ExcludeCharacters))
uIndex %= (ulong)pwCharSet.Size; pwCharSet.Remove(pwProfile.ExcludeCharacters);
char ch = pwCharSet[(uint)uIndex]; return true;
}
if (pwProfile.NoRepeatingCharacters) internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource)
pwCharSet.Remove(ch); {
if (v == null) { Debug.Assert(false); return; }
if (crsRandomSource == null) { Debug.Assert(false); return; }
return ch; for (int i = v.Length - 1; i >= 1; --i)
} {
int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1));
internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) char t = v[i];
{ v[i] = v[j];
pwCharSet.Remove(PwCharSet.Invalid); v[j] = t;
}
}
if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); private static PwgError GenerateCustom(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crs,
CustomPwGeneratorPool pwAlgorithmPool)
{
psOut = ProtectedString.Empty;
if (pwProfile.ExcludeCharacters.Length > 0) Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom);
pwCharSet.Remove(pwProfile.ExcludeCharacters); if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm;
}
internal static void ShufflePassword(char[] pPassword, string strID = pwProfile.CustomAlgorithmUuid;
CryptoRandomStream crsRandomSource) if (string.IsNullOrEmpty(strID)) return PwgError.UnknownAlgorithm;
{
Debug.Assert(pPassword != null); if (pPassword == null) return;
Debug.Assert(crsRandomSource != null); if (crsRandomSource == null) return;
if (pPassword.Length <= 1) return; // Nothing to shuffle byte[] pbUuid = Convert.FromBase64String(strID);
PwUuid uuid = new PwUuid(pbUuid);
CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid);
if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
for (int nSelect = 0; nSelect < pPassword.Length; ++nSelect) ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs);
{ if (pwd == null) return PwgError.Unknown;
ulong uRandomIndex = crsRandomSource.GetRandomUInt64();
uRandomIndex %= (ulong)(pPassword.Length - nSelect);
char chTemp = pPassword[nSelect]; psOut = pwd;
pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex]; return PwgError.Success;
pPassword[nSelect + (int)uRandomIndex] = chTemp; }
}
}
private static PwgError GenerateCustom(out ProtectedString psOut, internal static string ErrorToString(PwgError e, bool bHeader)
PwProfile pwProfile, CryptoRandomStream crs, {
CustomPwGeneratorPool pwAlgorithmPool) if (e == PwgError.Success) { Debug.Assert(false); return string.Empty; }
{ if ((e == PwgError.Unknown) && bHeader) return KLRes.PwGenFailed;
psOut = ProtectedString.Empty;
Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom); string str = KLRes.UnknownError;
if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; switch (e)
{
// case PwgError.Success:
// break;
string strID = pwProfile.CustomAlgorithmUuid; case PwgError.Unknown:
if (string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } break;
byte[] pbUuid = Convert.FromBase64String(strID); case PwgError.TooFewCharacters:
PwUuid uuid = new PwUuid(pbUuid); str = KLRes.CharSetTooFewChars;
CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid); break;
if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs); case PwgError.UnknownAlgorithm:
if (pwd == null) return PwgError.Unknown; str = KLRes.AlgorithmUnknown;
break;
psOut = pwd; case PwgError.InvalidCharSet:
return PwgError.Success; 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,114 +19,115 @@
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;
namespace KeePassLib.Cryptography 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
{ {
int iMaxLen = 0; Debug.Assert(g_dicts.Count > 0); // Should be initialized
foreach(int iLen in m_dicts.Keys)
{
if(iLen > iMaxLen) iMaxLen = iLen;
}
return iMaxLen; int iMaxLen = 0;
} foreach (int iLen in g_dicts.Keys)
} {
if (iLen > iMaxLen) iMaxLen = iLen;
}
internal static bool ContainsLength(int nLength) return iMaxLen;
{ }
Dictionary<string, bool> dDummy; }
return m_dicts.TryGetValue(nLength, out dDummy);
}
public static bool IsPopularPassword(char[] vPassword) internal static bool ContainsLength(int nLength)
{ {
ulong uDummy; Dictionary<char[], bool> dDummy;
return IsPopularPassword(vPassword, out uDummy); return g_dicts.TryGetValue(nLength, out dDummy);
} }
public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize) public static bool IsPopularPassword(char[] vPassword)
{ {
if(vPassword == null) throw new ArgumentNullException("vPassword"); ulong uDummy;
if(vPassword.Length == 0) { uDictSize = 0; return false; } return IsPopularPassword(vPassword, out uDummy);
}
string str = new string(vPassword); public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize)
{
if (vPassword == null) throw new ArgumentNullException("vPassword");
if (vPassword.Length == 0) { uDictSize = 0; return false; }
try { return IsPopularPasswordPriv(str, out uDictSize); } #if DEBUG
catch(Exception) { Debug.Assert(false); } Array.ForEach(vPassword, ch => Debug.Assert(ch == char.ToLower(ch)));
#endif
uDictSize = 0; try { return IsPopularPasswordPriv(vPassword, out uDictSize); }
return false; catch (Exception) { Debug.Assert(false); }
}
private static bool IsPopularPasswordPriv(string str, out ulong uDictSize) uDictSize = 0;
{ return false;
Debug.Assert(m_dicts.Count > 0); // Should be initialized with data }
Dictionary<string, bool> d; private static bool IsPopularPasswordPriv(char[] vPassword, out ulong uDictSize)
if(!m_dicts.TryGetValue(str.Length, out d)) {
{ Debug.Assert(g_dicts.Count > 0); // Should be initialized with data
uDictSize = 0;
return false;
}
uDictSize = (ulong)d.Count; Dictionary<char[], bool> d;
return d.ContainsKey(str); if (!g_dicts.TryGetValue(vPassword.Length, out d))
} {
uDictSize = 0;
return false;
}
public static void Add(byte[] pbData, bool bGZipped) uDictSize = (ulong)d.Count;
{ return d.ContainsKey(vPassword);
try }
{
if(bGZipped)
pbData = MemUtil.Decompress(pbData);
string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); public static void Add(byte[] pbData, bool bGZipped)
if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } {
try
{
if (bGZipped)
pbData = MemUtil.Decompress(pbData);
if(!char.IsWhiteSpace(strData[strData.Length - 1])) string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length);
strData += "\n"; if (string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; }
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);
} }
} }
else sb.Append(char.ToLower(ch)); else sb.Append(char.ToLower(ch));
} }
} }
catch(Exception) { Debug.Assert(false); } catch (Exception) { Debug.Assert(false); }
} }
} }
} }

File diff suppressed because it is too large Load Diff

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);
} }

File diff suppressed because it is too large Load Diff

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,10 +67,15 @@ 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()
{ {
_design = new ActivityDesign(this); _design = new ActivityDesign(this);
@@ -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,51 +477,76 @@ 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;
var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength);
progressBar.Progress = (int)passwordBits;
progressBar.Max = 128;
Color color = new Color(196, 63, 49);
if (passwordBits > 40)
{
color = new Color(219, 152, 55);
}
if (passwordBits > 64)
{
color = new Color(96, 138, 38);
}
if (passwordBits > 100)
{
color = new Color(31, 128, 31);
}
progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color,
PorterDuff.Mode.SrcIn));
FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits";
UpdateProfileSpinnerSelection(); 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);
progressBar.Progress = (int)passwordBits;
progressBar.Max = 128;
Color color = new Color(196, 63, 49);
if (passwordBits > 40)
{
color = new Color(219, 152, 55);
}
if (passwordBits > 64)
{
color = new Color(96, 138, 38);
}
if (passwordBits > 100)
{
color = new Color(31, 128, 31);
}
progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color,
PorterDuff.Mode.SrcIn));
FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits";
});
}
private void UpdateProfileSpinnerSelection() private void UpdateProfileSpinnerSelection()
{ {
int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex(); int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex();

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,19 +79,20 @@ android:paddingRight="16dp"
android:paddingTop="16dp"> android:paddingTop="16dp">
<TextView
android:id="@+id/QuickUnlock_label"
android:text="@string/QuickUnlock_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/filename_label"
android:textSize="14sp"
/>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal"
android:id="@+id/QuickUnlockForm">
<TextView
android:id="@+id/QuickUnlock_label"
android:text="@string/QuickUnlock_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/filename_label"
android:textSize="14sp"
/>
<EditText <EditText
android:inputType="textPassword" android:inputType="textPassword"
@@ -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>
@@ -559,7 +560,7 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="filestoragename_onedrive2_appfolder">Ordner der Keepass2Android-App</string> <string name="filestoragename_onedrive2_appfolder">Ordner der Keepass2Android-App</string>
<string name="filestoragename_sftp">SFTP (SSH File Transfer)</string> <string name="filestoragename_sftp">SFTP (SSH File Transfer)</string>
<string name="filestoragename_mega">MEGA</string> <string name="filestoragename_mega">MEGA</string>
<string name="filestoragehelp_mega">Bitte beachte: Keepass2Android muss eineListe aller Dateien des Mega-Kontos herunterladen, um ordnungsgemäß zu funktionieren. Aus diesem Grund ist der Zugriff auf Konten mit vielen Dateien möglicherweise langsam.</string> <string name="filestoragehelp_mega">Bitte beachte: Keepass2Android muss eine Liste aller Dateien des Mega-Kontos herunterladen, um ordnungsgemäß zu funktionieren. Aus diesem Grund ist der Zugriff auf Konten mit vielen Dateien möglicherweise langsam.</string>
<string name="filestoragename_content">Android-Dateibrowser</string> <string name="filestoragename_content">Android-Dateibrowser</string>
<string name="filestorage_setup_title">Dateizugriff initialisieren</string> <string name="filestorage_setup_title">Dateizugriff initialisieren</string>
<string name="database_location">Speicherort der Datenbank</string> <string name="database_location">Speicherort der Datenbank</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,7 +351,10 @@ namespace keepass2android
QuickUnlockEnabled = enabled; QuickUnlockEnabled = enabled;
} }
public bool QuickUnlockEnabled { get; private set; } public bool ScreenLockWasEnabledWhenOpeningDatabase { get; set; }
public bool QuickUnlockEnabled { get; private set; }
public int QuickUnlockKeyLength { get; private set; } public int QuickUnlockKeyLength { get; private set; }