Compare commits
33 Commits
Version_1.
...
master
Author | SHA1 | Date |
---|---|---|
Robinson | 23b9a4f7ff | |
Robinson | bc5c28ef08 | |
Robinson | 81588e2a8e | |
Robinson | da9c34b538 | |
Robinson | efcdbf9559 | |
Robinson | e13205166a | |
Robinson | 0ce4bb2e2e | |
Robinson | dc1bfb8371 | |
Robinson | 3bf845f355 | |
Robinson | 0ff5f377d1 | |
Robinson | 5fa2e2aff6 | |
Robinson | db0be638b4 | |
Robinson | ed5d2d2f74 | |
Robinson | ff21f4353e | |
Robinson | 0561f66e8e | |
Robinson | 869b3b4167 | |
Robinson | a0d02c83fc | |
Robinson | 11c30aed96 | |
Robinson | d3a767806b | |
Robinson | 27921e22ff | |
Robinson | be2dfae824 | |
Robinson | 9c8f6c97dc | |
Robinson | f874a0da68 | |
Robinson | 26a05099e9 | |
Robinson | f2782248db | |
Robinson | 298e760ee5 | |
Robinson | 52495cbe48 | |
Robinson | b12a86d9bc | |
Robinson | c4a6c98540 | |
Robinson | 61fa96539f | |
Robinson | 1b5827e33a | |
Robinson | c1f1ead3fb | |
Robinson | 18f7b2a2b8 |
269
LICENSE
269
LICENSE
|
@ -12,23 +12,6 @@
|
|||
Sean Luke
|
||||
Michael Lecuyer (portions Copyright 1993
|
||||
|
||||
- FileUtil (code from FilenameUtils.java for normalize + dependencies) -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://commons.apache.org/proper/commons-io/
|
||||
Copyright 2013
|
||||
The Apache Software Foundation
|
||||
Kevin A. Burton
|
||||
Scott Sanders
|
||||
Daniel Rall
|
||||
Christoph.Reck
|
||||
Peter Donald
|
||||
Jeff Turner
|
||||
Matthew Hawthorne
|
||||
Martin Cooper
|
||||
Jeremias Maerki
|
||||
Stephen Colebourne
|
||||
|
||||
- FastThreadLocal -
|
||||
[BSD 3-Clause License]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
|
@ -38,28 +21,6 @@
|
|||
Lightweight Java Game Library Project
|
||||
Riven
|
||||
|
||||
- Base64Fast -
|
||||
[BSD 3-Clause License]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://migbase64.sourceforge.net/
|
||||
Copyright 2004
|
||||
Mikael Grev, MiG InfoCom AB. (base64@miginfocom.com)
|
||||
|
||||
- BCrypt -
|
||||
[BSD 2-Clause "Simplified" or "FreeBSD" license]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://www.mindrot.org/projects/jBCrypt
|
||||
Copyright 2006
|
||||
Damien Miller (djm@mindrot.org)
|
||||
GWT modified version
|
||||
|
||||
- Modified hex conversion utility methods -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://netty.io
|
||||
Copyright 2014
|
||||
The Netty Project
|
||||
|
||||
- Retrofit - A type-safe HTTP client for Android and Java
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/square/retrofit
|
||||
|
@ -86,6 +47,12 @@
|
|||
Copyright 2018
|
||||
Venkat Peri
|
||||
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2023
|
||||
JetBrains s.r.o.
|
||||
|
||||
- Java Uuid Generator - A set of Java classes for working with UUIDs
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/cowtowncoder/java-uuid-generator
|
||||
|
@ -93,44 +60,6 @@
|
|||
Tatu Saloranta (tatu.saloranta@iki.fi)
|
||||
Contributors. See source release-notes/CREDITS
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
https://www.slf4j.org
|
||||
Copyright 2023
|
||||
QOS.ch
|
||||
|
||||
- XZ for Java - Complete implementation of XZ data compression in pure Java
|
||||
[Public Domain, per Creative Commons CC0]
|
||||
https://tukaani.org/xz/java.html
|
||||
Copyright 2023
|
||||
Lasse Collin
|
||||
Igor Pavlov
|
||||
|
||||
- Netty - An event-driven asynchronous network application framework
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://netty.io
|
||||
Copyright 2023
|
||||
The Netty Project
|
||||
Contributors. See source NOTICE
|
||||
|
||||
- Bouncy Castle Crypto - Lightweight cryptography API and JCE Extension
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://www.bouncycastle.org
|
||||
Copyright 2023
|
||||
The Legion of the Bouncy Castle Inc
|
||||
|
||||
- Lightweight Java Game Library - Java library that enables cross-platform access to popular native APIs
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/LWJGL/lwjgl3
|
||||
Copyright 2023
|
||||
Lightweight Java Game Library
|
||||
|
||||
- TypeTools - A simple, zero-dependency library for working with types. Supports Java 1.6+ and Android.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/jhalterman/typetools
|
||||
Copyright 2023
|
||||
Jonathan Halterman and friends
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
|
@ -139,192 +68,6 @@
|
|||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2023
|
||||
JetBrains s.r.o.
|
||||
|
||||
- Collections - Niche collections to augment what is already available.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
Copyright 2023
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Bias, BinarySearch -
|
||||
[MIT License]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/timboudreau/util
|
||||
Copyright 2013
|
||||
Tim Boudreau
|
||||
|
||||
- ConcurrentEntry -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
Copyright 2016
|
||||
bennidi
|
||||
dorkbox
|
||||
|
||||
- Collection Utilities (Array, ArrayMap, BooleanArray, ByteArray, CharArray, FloatArray, IdentityMap, IntArray, IntFloatMap, IntIntMap, IntMap, IntSet, LongArray, LongMap, ObjectFloatMap, ObjectIntMap, ObjectMap, ObjectSet, OrderedMap, OrderedSet) -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2011
|
||||
LibGDX
|
||||
Mario Zechner (badlogicgames@gmail.com)
|
||||
Nathan Sweet (nathan.sweet@gmail.com)
|
||||
|
||||
- Predicate -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2011
|
||||
LibGDX
|
||||
Mario Zechner (badlogicgames@gmail.com)
|
||||
Nathan Sweet (nathan.sweet@gmail.com)
|
||||
xoppa
|
||||
|
||||
- Select, QuickSelect -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2011
|
||||
LibGDX
|
||||
Mario Zechner (badlogicgames@gmail.com)
|
||||
Nathan Sweet (nathan.sweet@gmail.com)
|
||||
Jon Renner
|
||||
|
||||
- TimSort, ComparableTimSort -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Collections
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2008
|
||||
The Android Open Source Project
|
||||
|
||||
- ConcurrentWeakIdentityHashMap - Concurrent WeakIdentity HashMap
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/spring-projects/spring-loaded/blob/master/springloaded/src/main/java/org/springsource/loaded/support/ConcurrentWeakIdentityHashMap.java
|
||||
Copyright 2016
|
||||
zhanhb
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 8+
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Executor
|
||||
Copyright 2023
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- ZT Process Executor -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/zeroturnaround/zt-exec
|
||||
Copyright 2014
|
||||
ZeroTurnaround LLC
|
||||
|
||||
- Apache Commons Exec -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-exec/
|
||||
Copyright 2014
|
||||
The Apache Software Foundation
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2023
|
||||
JetBrains s.r.o.
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
https://www.slf4j.org
|
||||
Copyright 2023
|
||||
QOS.ch
|
||||
|
||||
- Logback - Logback is a logging framework for Java applications
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://logback.qos.ch
|
||||
Copyright 2023
|
||||
QOS.ch
|
||||
|
||||
- SSHJ - SSHv2 library for Java
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/hierynomus/sshj
|
||||
Copyright 2023
|
||||
Jeroen van Erp
|
||||
SSHJ Contributors
|
||||
|
||||
Extra license information
|
||||
- Apache MINA -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://mina.apache.org/sshd-project/
|
||||
The Apache Software Foundation
|
||||
|
||||
- Apache Commons-Net -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-net/
|
||||
The Apache Software Foundation
|
||||
|
||||
- JZlib -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/ymnk/jzlib
|
||||
Atsuhiko Yamanaka
|
||||
JCraft, Inc.
|
||||
|
||||
- Bouncy Castle Crypto -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://www.bouncycastle.org
|
||||
The Legion of the Bouncy Castle Inc
|
||||
|
||||
- ed25519-java -
|
||||
[Public Domain, per Creative Commons CC0]
|
||||
https://github.com/str4d/ed25519-java
|
||||
https://github.com/str4d
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- OS - Information about the system, Java runtime, OS, Window Manager, and Desktop Environment.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/OS
|
||||
|
|
22
LICENSE.BSD2
22
LICENSE.BSD2
|
@ -1,22 +0,0 @@
|
|||
BSD License
|
||||
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <ORGANIZATION> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
121
LICENSE.CC0
121
LICENSE.CC0
|
@ -1,121 +0,0 @@
|
|||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
21
LICENSE.MIT
21
LICENSE.MIT
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
12
README.md
12
README.md
|
@ -11,25 +11,25 @@ Please see the header of each file for the specific license that applies to it.
|
|||
|
||||
Maven Info
|
||||
---------
|
||||
````
|
||||
```
|
||||
<dependencies>
|
||||
...
|
||||
<dependency>
|
||||
<groupId>com.dorkbox</groupId>
|
||||
<artifactId>Utilities</artifactId>
|
||||
<version>1.36</version>
|
||||
<version>1.48</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
````
|
||||
```
|
||||
|
||||
Gradle Info
|
||||
---------
|
||||
````
|
||||
```
|
||||
dependencies {
|
||||
...
|
||||
compile "com.dorkbox:Utilities:1.36"
|
||||
compile "com.dorkbox:Utilities:1.48"
|
||||
}
|
||||
````
|
||||
```
|
||||
|
||||
|
||||
License
|
||||
|
|
|
@ -23,19 +23,19 @@
|
|||
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace!
|
||||
|
||||
plugins {
|
||||
id("com.dorkbox.GradleUtils") version "3.17"
|
||||
id("com.dorkbox.Licensing") version "2.24"
|
||||
id("com.dorkbox.GradleUtils") version "3.18"
|
||||
id("com.dorkbox.Licensing") version "2.28"
|
||||
id("com.dorkbox.VersionUpdate") version "2.8"
|
||||
id("com.dorkbox.GradlePublish") version "1.18"
|
||||
id("com.dorkbox.GradlePublish") version "1.20"
|
||||
|
||||
kotlin("jvm") version "1.8.0"
|
||||
kotlin("jvm") version "1.9.0"
|
||||
}
|
||||
|
||||
object Extras {
|
||||
// set for the project
|
||||
const val description = "Utilities for use within Java projects"
|
||||
const val group = "com.dorkbox"
|
||||
const val version = "1.41"
|
||||
const val version = "1.48"
|
||||
|
||||
// set as project.ext
|
||||
const val name = "Utilities"
|
||||
|
@ -66,22 +66,6 @@ licensing {
|
|||
author("Sean Luke")
|
||||
author("Michael Lecuyer (portions Copyright 1993")
|
||||
}
|
||||
extra("FileUtil (code from FilenameUtils.java for normalize + dependencies)", License.APACHE_2) {
|
||||
url(Extras.url)
|
||||
url("https://commons.apache.org/proper/commons-io/")
|
||||
copyright(2013)
|
||||
author("The Apache Software Foundation")
|
||||
author("Kevin A. Burton")
|
||||
author("Scott Sanders")
|
||||
author("Daniel Rall")
|
||||
author("Christoph.Reck")
|
||||
author("Peter Donald")
|
||||
author("Jeff Turner")
|
||||
author("Matthew Hawthorne")
|
||||
author("Martin Cooper")
|
||||
author("Jeremias Maerki")
|
||||
author("Stephen Colebourne")
|
||||
}
|
||||
extra("FastThreadLocal", License.BSD_3) {
|
||||
url(Extras.url)
|
||||
url("https://github.com/LWJGL/lwjgl3/blob/5819c9123222f6ce51f208e022cb907091dd8023/modules/core/src/main/java/org/lwjgl/system/FastThreadLocal.java")
|
||||
|
@ -90,25 +74,7 @@ licensing {
|
|||
author("Lightweight Java Game Library Project")
|
||||
author("Riven")
|
||||
}
|
||||
extra("Base64Fast", License.BSD_3) {
|
||||
url(Extras.url)
|
||||
url("https://migbase64.sourceforge.net/")
|
||||
copyright(2004)
|
||||
author("Mikael Grev, MiG InfoCom AB. (base64@miginfocom.com)")
|
||||
}
|
||||
extra("BCrypt", License.BSD_2) {
|
||||
url(Extras.url)
|
||||
url("https://www.mindrot.org/projects/jBCrypt")
|
||||
copyright(2006)
|
||||
author("Damien Miller (djm@mindrot.org)")
|
||||
note("GWT modified version")
|
||||
}
|
||||
extra("Modified hex conversion utility methods", License.APACHE_2) {
|
||||
url(Extras.url)
|
||||
url("https://netty.io")
|
||||
copyright(2014)
|
||||
author("The Netty Project")
|
||||
}
|
||||
|
||||
extra("Retrofit", License.APACHE_2) {
|
||||
copyright(2020)
|
||||
description("A type-safe HTTP client for Android and Java")
|
||||
|
@ -155,11 +121,9 @@ tasks.jar.get().apply {
|
|||
// NOTE: compileOnly is used because there are some classes/dependencies that ARE NOT necessary to be included, UNLESS the user
|
||||
// is actually using that part of the library. If this happens, they will (or should) already be using the dependency)
|
||||
dependencies {
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
||||
|
||||
api("com.dorkbox:Collections:1.6")
|
||||
api("com.dorkbox:Executor:3.13")
|
||||
api("com.dorkbox:OS:1.6")
|
||||
api("com.dorkbox:OS:1.8")
|
||||
api("com.dorkbox:Updates:1.1")
|
||||
|
||||
|
||||
|
@ -169,46 +133,19 @@ dependencies {
|
|||
|
||||
// // https://github.com/MicroUtils/kotlin-logging NO JPMS SUPPORT!
|
||||
// api("io.github.microutils:kotlin-logging:3.0.4")
|
||||
api("org.slf4j:slf4j-api:2.0.7")
|
||||
// api("org.slf4j:slf4j-api:2.0.7")
|
||||
|
||||
api("org.tukaani:xz:1.9")
|
||||
compileOnly("com.fasterxml.uuid:java-uuid-generator:4.1.0")
|
||||
|
||||
// api "com.koloboke:koloboke-api-jdk8:1.0.0"
|
||||
// runtime "com.koloboke:koloboke-impl-jdk8:1.0.0"
|
||||
|
||||
// compileOnly("com.esotericsoftware:kryo:5.3.0")
|
||||
// compileOnly("de.javakaffee:kryo-serializers:0.45")
|
||||
|
||||
compileOnly("io.netty:netty-buffer:4.1.94.Final")
|
||||
|
||||
val bcVersion = "1.70"
|
||||
compileOnly("org.bouncycastle:bcprov-jdk15on:$bcVersion")
|
||||
compileOnly("org.bouncycastle:bcpg-jdk15on:$bcVersion")
|
||||
compileOnly("org.bouncycastle:bcmail-jdk15on:$bcVersion")
|
||||
compileOnly("org.bouncycastle:bctls-jdk15on:$bcVersion")
|
||||
|
||||
compileOnly("org.lwjgl:lwjgl-xxhash:3.3.2")
|
||||
|
||||
compileOnly("net.jodah:typetools:0.6.3")
|
||||
// compileOnly("io.netty:netty-buffer:4.1.96.Final")
|
||||
|
||||
|
||||
// testing
|
||||
testImplementation("org.bouncycastle:bcprov-jdk15on:$bcVersion")
|
||||
testImplementation("org.bouncycastle:bcpg-jdk15on:$bcVersion")
|
||||
testImplementation("org.bouncycastle:bcmail-jdk15on:$bcVersion")
|
||||
testImplementation("org.bouncycastle:bctls-jdk15on:$bcVersion")
|
||||
|
||||
testImplementation("com.esotericsoftware:kryo:5.4.0")
|
||||
testImplementation("de.javakaffee:kryo-serializers:0.45")
|
||||
|
||||
testImplementation("com.dorkbox:Serializers:2.7")
|
||||
|
||||
testImplementation("com.dorkbox:Executor:3.13")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("ch.qos.logback:logback-classic:1.4.5")
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
// testImplementation("ch.qos.logback:logback-classic:1.4.5")
|
||||
// implementation(kotlin("stdlib-jdk8"))
|
||||
}
|
||||
|
||||
publishToSonatype {
|
||||
|
|
Binary file not shown.
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
@ -80,10 +80,10 @@ do
|
|||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
@ -143,12 +143,16 @@ fi
|
|||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
|
|
@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
|||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,3 +13,4 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
rootProject.name = "Utilities"
|
||||
|
|
|
@ -18,7 +18,6 @@ package dorkbox.util
|
|||
import dorkbox.os.OS.TEMP_DIR
|
||||
import dorkbox.util.FileUtil.copyFile
|
||||
import dorkbox.util.FileUtil.delete
|
||||
import dorkbox.util.FileUtil.getExtension
|
||||
import java.io.*
|
||||
import java.math.BigInteger
|
||||
import java.net.URL
|
||||
|
@ -31,7 +30,7 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
* Clears ALL saved files in the cache
|
||||
*/
|
||||
fun clear() {
|
||||
// deletes all of the files (recursively) in the specified location. If the directory is empty (no locked files), then the
|
||||
// deletes all the files (recursively) in the specified location. If the directory is empty (no locked files), then the
|
||||
// directory is also deleted.
|
||||
delete(File(TEMP_DIR, tempDir))
|
||||
}
|
||||
|
@ -71,10 +70,7 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
/**
|
||||
* Checks to see if the specified URL is in the cache. NULL if it is not, otherwise specifies a location on disk.
|
||||
*/
|
||||
fun check(fileResource: URL?): File? {
|
||||
if (fileResource == null) {
|
||||
throw NullPointerException("fileResource")
|
||||
}
|
||||
fun check(fileResource: URL): File? {
|
||||
return check(fileResource.path)
|
||||
}
|
||||
|
||||
|
@ -83,10 +79,7 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
* specifies a location on disk.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun check(fileStream: InputStream?): File? {
|
||||
if (fileStream == null) {
|
||||
throw NullPointerException("fileStream")
|
||||
}
|
||||
fun check(fileStream: InputStream): File? {
|
||||
return check(null, fileStream)
|
||||
}
|
||||
|
||||
|
@ -95,17 +88,13 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
* cacheName is NULL, it will use a HASH of the fileStream
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun check(cacheName: String?, fileStream: InputStream?): File? {
|
||||
var cacheName = cacheName
|
||||
if (fileStream == null) {
|
||||
throw NullPointerException("fileStream")
|
||||
}
|
||||
if (cacheName == null) {
|
||||
cacheName = createNameAsHash(fileStream)
|
||||
}
|
||||
|
||||
fun check(cacheName: String?, fileStream: InputStream): File? {
|
||||
// if we already have this fileName, reuse it
|
||||
val newFile = makeCacheFile(cacheName)
|
||||
val newFile = if (cacheName == null) {
|
||||
makeCacheFile(createNameAsHash(fileStream))
|
||||
} else {
|
||||
makeCacheFile(cacheName)
|
||||
}
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
return if (newFile.canRead() && newFile.isFile) {
|
||||
|
@ -126,11 +115,11 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(cacheName: String?, file: File): File {
|
||||
var cacheName = cacheName
|
||||
if (cacheName == null) {
|
||||
cacheName = file.absolutePath
|
||||
return if (cacheName == null) {
|
||||
save(file.absolutePath, file.absolutePath)
|
||||
} else {
|
||||
save(cacheName, file.absolutePath)
|
||||
}
|
||||
return save(cacheName, file.absolutePath)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,12 +137,10 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(cacheName: String?, fileName: String): File {
|
||||
var cacheName = cacheName
|
||||
if (cacheName == null) {
|
||||
cacheName = fileName
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val cacheName = cacheName ?: fileName
|
||||
|
||||
val newFile = makeCacheFile(cacheName)
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
|
@ -195,12 +182,10 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(cacheName: String?, fileResource: URL): File {
|
||||
var cacheName = cacheName
|
||||
if (cacheName == null) {
|
||||
cacheName = fileResource.path
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val cacheName = cacheName ?: fileResource.path
|
||||
|
||||
val newFile = makeCacheFile(cacheName)
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
|
@ -230,12 +215,10 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
fun save(cacheName: String?, fileStream: InputStream): File {
|
||||
var cacheName = cacheName
|
||||
if (cacheName == null) {
|
||||
cacheName = createNameAsHash(fileStream)
|
||||
}
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val cacheName = cacheName ?: createNameAsHash(fileStream)
|
||||
|
||||
val newFile = makeCacheFile(cacheName)
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
|
@ -253,13 +236,7 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
* @return the full path of the resource copied to disk, or NULL if invalid
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun makeFileViaStream(cacheName: String?, resourceStream: InputStream?): File {
|
||||
if (resourceStream == null) {
|
||||
throw NullPointerException("resourceStream")
|
||||
}
|
||||
if (cacheName == null) {
|
||||
throw NullPointerException("cacheName")
|
||||
}
|
||||
private fun makeFileViaStream(cacheName: String, resourceStream: InputStream): File {
|
||||
val newFile = makeCacheFile(cacheName)
|
||||
|
||||
// if this file already exists (via HASH), we just reuse what is saved on disk.
|
||||
|
@ -298,22 +275,19 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
*
|
||||
* @return the file on disk represented by the file name
|
||||
*/
|
||||
fun create(cacheName: String?): File {
|
||||
fun create(cacheName: String): File {
|
||||
return makeCacheFile(cacheName)
|
||||
}
|
||||
|
||||
// creates the file that will be cached. It may, or may not already exist
|
||||
// must be called from synchronized block!
|
||||
// never returns null
|
||||
private fun makeCacheFile(cacheName: String?): File {
|
||||
if (cacheName == null) {
|
||||
throw NullPointerException("cacheName")
|
||||
}
|
||||
private fun makeCacheFile(cacheName: String): File {
|
||||
val saveDir = File(TEMP_DIR, tempDir)
|
||||
|
||||
// can be wimpy, only one at a time
|
||||
val hash = hashName(cacheName)
|
||||
var extension = getExtension(cacheName)
|
||||
var extension = Sys.getExtension(cacheName)
|
||||
if (extension.isEmpty()) {
|
||||
extension = "cache"
|
||||
}
|
||||
|
@ -324,6 +298,11 @@ class CacheUtil(private val tempDir: String = "cache") {
|
|||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private val digestLocal = ThreadLocal.withInitial {
|
||||
try {
|
||||
return@withInitial MessageDigest.getInstance("SHA1")
|
||||
|
|
|
@ -66,6 +66,12 @@ inline fun ignoreExceptions(vararg blocks: () -> Unit) {
|
|||
}
|
||||
}
|
||||
|
||||
fun Mutex.safeUnlock() {
|
||||
if (isLocked) {
|
||||
unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// From: https://elizarov.medium.com/phantom-of-the-coroutine-afc63b03a131
|
||||
suspend inline fun <T> Mutex.withReentrantLock(crossinline block: suspend () -> T): T {
|
||||
val key = ReentrantMutexContextKey(this)
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public
|
||||
class DelayTimer {
|
||||
private final String name;
|
||||
private final boolean isDaemon;
|
||||
private final Runnable listener;
|
||||
private volatile Timer timer;
|
||||
private long delay;
|
||||
|
||||
public
|
||||
DelayTimer(Runnable listener) {
|
||||
this(null, true, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes you want to make sure that this timer will complete, even if the calling thread has terminated.
|
||||
*
|
||||
* @param name the name of the thread (if you want to specify one)
|
||||
* @param isDaemon true if you want this timer to be run on a daemon thread
|
||||
* @param listener the callback listener to execute
|
||||
*/
|
||||
public
|
||||
DelayTimer(String name, boolean isDaemon, Runnable listener) {
|
||||
this.name = name;
|
||||
this.listener = listener;
|
||||
this.isDaemon = isDaemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this timer is still waiting to run.
|
||||
*/
|
||||
public synchronized
|
||||
boolean isWaiting() {
|
||||
return this.timer != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the delay timer!
|
||||
*/
|
||||
public synchronized
|
||||
void cancel() {
|
||||
if (this.timer != null) {
|
||||
this.timer.cancel();
|
||||
this.timer.purge();
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delay milliseconds to wait
|
||||
*/
|
||||
public synchronized
|
||||
void delay(long delay) {
|
||||
this.delay = delay;
|
||||
cancel();
|
||||
|
||||
if (delay > 0) {
|
||||
if (this.name != null) {
|
||||
this.timer = new Timer(this.name, this.isDaemon);
|
||||
}
|
||||
else {
|
||||
this.timer = new Timer(this.isDaemon);
|
||||
}
|
||||
|
||||
TimerTask t = new TimerTask() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// timer can change if the callback calls delay() or cancel()
|
||||
Timer origTimer = DelayTimer.this.timer;
|
||||
|
||||
DelayTimer.this.listener.run();
|
||||
|
||||
if (origTimer != null) {
|
||||
origTimer.cancel();
|
||||
origTimer.purge();
|
||||
|
||||
if (origTimer == DelayTimer.this.timer) {
|
||||
DelayTimer.this.timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
this.timer.schedule(t, delay);
|
||||
}
|
||||
else {
|
||||
this.listener.run();
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized
|
||||
long getDelay() {
|
||||
return this.delay;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Sometimes you want to make sure that this timer will complete, even if the calling thread has terminated.
|
||||
*/
|
||||
class DelayTimer(
|
||||
/**
|
||||
* the name of the thread (if you want to specify one)
|
||||
*/
|
||||
private val name: String,
|
||||
/**
|
||||
* true if you want this timer to be run on a daemon thread
|
||||
*/
|
||||
private val isDaemon: Boolean,
|
||||
/**
|
||||
* the callback listener to execute
|
||||
*/
|
||||
private val listener: Runnable) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
}
|
||||
|
||||
@Volatile
|
||||
private var timer: Timer? = null
|
||||
|
||||
@get:Synchronized
|
||||
var delay: Long = 0
|
||||
private set
|
||||
|
||||
constructor(listener: Runnable) : this("Timer-", true, listener)
|
||||
|
||||
@get:Synchronized
|
||||
val isWaiting: Boolean
|
||||
/**
|
||||
* @return true if this timer is still waiting to run.
|
||||
*/
|
||||
get() = timer != null
|
||||
|
||||
/**
|
||||
* Cancel the delay timer!
|
||||
*/
|
||||
@Synchronized
|
||||
fun cancel() {
|
||||
if (timer != null) {
|
||||
timer!!.cancel()
|
||||
timer!!.purge()
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delay milliseconds to wait
|
||||
*/
|
||||
@Synchronized
|
||||
fun delay(delay: Long) {
|
||||
this.delay = delay
|
||||
cancel()
|
||||
|
||||
if (delay > 0) {
|
||||
timer = Timer(name, isDaemon)
|
||||
val t: TimerTask = object : TimerTask() {
|
||||
override fun run() {
|
||||
// timer can change if the callback calls delay() or cancel()
|
||||
val origTimer = timer
|
||||
listener.run()
|
||||
if (origTimer != null) {
|
||||
origTimer.cancel()
|
||||
origTimer.purge()
|
||||
if (origTimer === timer) {
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
timer!!.schedule(t, delay)
|
||||
} else {
|
||||
listener.run()
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -18,20 +18,7 @@ package dorkbox.util
|
|||
import dorkbox.os.OS
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedReader
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.PrintWriter
|
||||
import java.io.RandomAccessFile
|
||||
import java.io.Reader
|
||||
import java.io.*
|
||||
import java.nio.file.DirectoryIteratorException
|
||||
import java.util.*
|
||||
import java.util.zip.*
|
||||
|
@ -57,7 +44,7 @@ object FileUtil {
|
|||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = "1.41"
|
||||
val version = Sys.version
|
||||
|
||||
private val log: Logger = LoggerFactory.getLogger(FileUtil::class.java)
|
||||
|
||||
|
@ -569,12 +556,11 @@ object FileUtil {
|
|||
/**
|
||||
* Copies a files from one location to another. Overwriting any existing file at the destination.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun copyFile(`in`: File, out: File): File {
|
||||
val normalizedIn = normalize(`in`)!!.absolutePath
|
||||
val normalizedout = normalize(out)!!.absolutePath
|
||||
if (normalizedIn.equals(normalizedout, ignoreCase = true)) {
|
||||
val normalizedIn = `in`.normalize().absolutePath
|
||||
val normalizedOut = out.normalize().absolutePath
|
||||
if (normalizedIn.equals(normalizedOut, ignoreCase = true)) {
|
||||
if (DEBUG) {
|
||||
System.err.println("Source equals destination! $normalizedIn")
|
||||
}
|
||||
|
@ -662,8 +648,8 @@ object FileUtil {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
fun copyDirectory(src_: File, dest_: File, vararg namesToIgnore: String) {
|
||||
val src = normalize(src_)
|
||||
val dest = normalize(dest_)
|
||||
val src = src_.normalize()
|
||||
val dest = dest_.normalize()
|
||||
|
||||
requireNotNull(src) { "Source must be valid" }
|
||||
requireNotNull(dest) { "Destination must be valid" }
|
||||
|
@ -769,7 +755,6 @@ object FileUtil {
|
|||
*
|
||||
* @return true IFF the file/dir was deleted or didn't exist at first
|
||||
*/
|
||||
@JvmStatic
|
||||
fun delete(file: File, vararg namesToIgnore: String): Boolean {
|
||||
if (!file.exists()) {
|
||||
return true
|
||||
|
@ -785,7 +770,7 @@ object FileUtil {
|
|||
var delete = true
|
||||
val file2 = files[i]
|
||||
val name2 = file2.name
|
||||
val name2Full = normalize(file2)!!.absolutePath
|
||||
val name2Full = file2.normalize().absolutePath
|
||||
if (file2.isDirectory) {
|
||||
for (name in namesToIgnore) {
|
||||
if (name[0] == UNIX_SEPARATOR && name == name2) {
|
||||
|
@ -861,7 +846,7 @@ object FileUtil {
|
|||
/**
|
||||
* @return the contents of the file as a byte array
|
||||
*/
|
||||
fun toBytes(file: File): ByteArray? {
|
||||
fun toBytes(file: File): ByteArray {
|
||||
return file.readBytes()
|
||||
}
|
||||
|
||||
|
@ -869,7 +854,7 @@ object FileUtil {
|
|||
* Creates the directories in the specified location.
|
||||
*/
|
||||
fun mkdir(location: File): String {
|
||||
val path = normalize(location)!!.absoluteFile
|
||||
val path = location.normalize().absoluteFile
|
||||
if (location.mkdirs()) {
|
||||
if (DEBUG) {
|
||||
System.err.println("Created directory: $path")
|
||||
|
@ -890,7 +875,7 @@ object FileUtil {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
fun tempFile(fileName: String): File {
|
||||
return normalize(File.createTempFile(fileName, null))!!.absoluteFile
|
||||
return File.createTempFile(fileName, null).normalize().absoluteFile
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -905,7 +890,7 @@ object FileUtil {
|
|||
if (!file.mkdir()) {
|
||||
throw IOException("Unable to create temp directory: $file")
|
||||
}
|
||||
return normalize(file)!!.absolutePath
|
||||
return file.normalize().absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1045,16 +1030,15 @@ object FileUtil {
|
|||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun unjarzip1(inputStream: ZipInputStream, outputDir: File, extractManifest: Boolean) {
|
||||
|
||||
inputStream.use {
|
||||
var entry: ZipEntry
|
||||
var entry: ZipEntry?
|
||||
while (inputStream.nextEntry.also { entry = it } != null) {
|
||||
val name = entry.name
|
||||
val name = entry!!.name
|
||||
if (!extractManifest && name.startsWith("META-INF/")) {
|
||||
continue
|
||||
}
|
||||
val file = File(outputDir, name)
|
||||
if (entry.isDirectory) {
|
||||
if (entry!!.isDirectory) {
|
||||
mkdir(file.path)
|
||||
continue
|
||||
}
|
||||
|
@ -1068,7 +1052,7 @@ object FileUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses the specified root directory for **ALL** files that are in it. All of the sub-directories are searched as well.
|
||||
* Parses the specified root directory for **ALL** files that are in it. All the sub-directories are searched as well.
|
||||
*
|
||||
*
|
||||
* *This is different, in that it returns ALL FILES, instead of ones that just match a specific extension.*
|
||||
|
@ -1081,7 +1065,7 @@ object FileUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses the specified root directory for **ALL** files that are in it. All of the sub-directories are searched as well.
|
||||
* Parses the specified root directory for **ALL** files that are in it. All the sub-directories are searched as well.
|
||||
*
|
||||
*
|
||||
* *This is different, in that it returns ALL FILES, instead of ones that just match a specific extension.*
|
||||
|
@ -1094,7 +1078,7 @@ object FileUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses the specified root directory for files that end in the extension to match. All of the sub-directories are searched as well.
|
||||
* Parses the specified root directory for files that end in the extension to match. All the sub-directories are searched as well.
|
||||
*
|
||||
* @return the list of all files in the root+sub-dirs that match the given extension.
|
||||
*/
|
||||
|
@ -1104,7 +1088,7 @@ object FileUtil {
|
|||
val directories = LinkedList<File?>()
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val rootDirectory = normalize(rootDirectory) ?: throw IOException("Root directory was invalid!")
|
||||
val rootDirectory = rootDirectory.normalize()
|
||||
|
||||
if (!rootDirectory.exists()) {
|
||||
throw IOException("Location does not exist: " + rootDirectory.absolutePath)
|
||||
|
@ -1348,625 +1332,4 @@ object FileUtil {
|
|||
}
|
||||
return file.setLastModified(timestamp)
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps.
|
||||
*
|
||||
*
|
||||
* THIS IS DIFFERENT in that it might not be a path that resolves to anything
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format of the system.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be retained.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo/
|
||||
* /foo/./ --> /foo/
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar/
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo/
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar/
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* (Note the file separator returned will be correct for Windows/Unix)
|
||||
*
|
||||
* @param filename the filename to normalize, null returns null
|
||||
*
|
||||
* @return the normalized filename, or null if invalid
|
||||
*/
|
||||
fun normalizeRaw(filename: String): String? {
|
||||
return doNormalize(filename, SYSTEM_SEPARATOR, true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format of the system.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be retained.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo/
|
||||
* /foo/./ --> /foo/
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar/
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo/
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar/
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* (Note the file separator returned will be correct for Windows/Unix)
|
||||
*
|
||||
* @param filename the file to normalize, null returns null
|
||||
* @return the normalized file, or null if invalid
|
||||
*/
|
||||
fun normalize(filename: String): File? {
|
||||
val asString = doNormalize(File(filename).absolutePath, SYSTEM_SEPARATOR, true) ?: return null
|
||||
return File(asString).absoluteFile
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format of the system.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be retained.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo/
|
||||
* /foo/./ --> /foo/
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar/
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo/
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar/
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* (Note the file separator returned will be correct for Windows/Unix)
|
||||
*
|
||||
* @param file the file to normalize, null returns null
|
||||
* @return the normalized file, or null if invalid
|
||||
*/
|
||||
fun normalize(file: File): File? {
|
||||
val asString = doNormalize(file.absolutePath, SYSTEM_SEPARATOR, true) ?: return null
|
||||
return File(asString).absoluteFile
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format specified.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be retained.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo/
|
||||
* /foo/./ --> /foo/
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar/
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo/
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar/
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* The output will be the same on both Unix and Windows including
|
||||
* the separator character.
|
||||
*
|
||||
* @param filename the filename to normalize, null returns null
|
||||
* @param unixSeparator `true` if a unix separator should
|
||||
* be used or `false` if a windows separator should be used.
|
||||
* @return the normalized filename, or null if invalid
|
||||
*/
|
||||
fun normalize(filename: String, unixSeparator: Boolean): String? {
|
||||
val separator = if (unixSeparator) UNIX_SEPARATOR else WINDOWS_SEPARATOR
|
||||
return doNormalize(filename, separator, true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps,
|
||||
* and removing any final directory separator.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format of the system.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be removed.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows except
|
||||
* for the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo
|
||||
* /foo/./ --> /foo
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
* (Note the file separator returned will be correct for Windows/Unix)
|
||||
*
|
||||
* @param filename the filename to normalize, null returns null
|
||||
* @return the normalized filename, or null if invalid
|
||||
*/
|
||||
fun normalizeNoEndSeparator(filename: String): String? {
|
||||
return doNormalize(filename, SYSTEM_SEPARATOR, false)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Normalizes a path, removing double and single dot path steps,
|
||||
* and removing any final directory separator.
|
||||
*
|
||||
*
|
||||
* This method normalizes a path to a standard format.
|
||||
* The input may contain separators in either Unix or Windows format.
|
||||
* The output will contain separators in the format specified.
|
||||
*
|
||||
*
|
||||
* A trailing slash will be removed.
|
||||
* A double slash will be merged to a single slash (but UNC names are handled).
|
||||
* A single dot path segment will be removed.
|
||||
* A double dot will cause that path segment and the one before to be removed.
|
||||
* If the double dot has no parent path segment to work with, `null`
|
||||
* is returned.
|
||||
*
|
||||
*
|
||||
* The output will be the same on both Unix and Windows including
|
||||
* the separator character.
|
||||
* <pre>
|
||||
* /foo// --> /foo
|
||||
* /foo/./ --> /foo
|
||||
* /foo/../bar --> /bar
|
||||
* /foo/../bar/ --> /bar
|
||||
* /foo/../bar/../baz --> /baz
|
||||
* //foo//./bar --> /foo/bar
|
||||
* /../ --> null
|
||||
* ../foo --> null
|
||||
* foo/bar/.. --> foo
|
||||
* foo/../../bar --> null
|
||||
* foo/../bar --> bar
|
||||
* //server/foo/../bar --> //server/bar
|
||||
* //server/../bar --> null
|
||||
* C:\foo\..\bar --> C:\bar
|
||||
* C:\..\bar --> null
|
||||
* ~/foo/../bar/ --> ~/bar
|
||||
* ~/../bar --> null
|
||||
</pre> *
|
||||
*
|
||||
* @param filename the filename to normalize, null returns null
|
||||
* @param unixSeparator `true` if a unix separator should
|
||||
* be used or `false` if a windows separtor should be used.
|
||||
* @return the normalized filename, or null if invalid
|
||||
*/
|
||||
fun normalizeNoEndSeparator(filename: String, unixSeparator: Boolean): String? {
|
||||
val separator = if (unixSeparator) UNIX_SEPARATOR else WINDOWS_SEPARATOR
|
||||
return doNormalize(filename, separator, false)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Internal method to perform the normalization.
|
||||
*
|
||||
* @param filename the filename
|
||||
* @param separator The separator character to use
|
||||
* @param keepSeparator true to keep the final separator
|
||||
* @return the normalized filename
|
||||
*/
|
||||
private fun doNormalize(filename: String, separator: Char, keepSeparator: Boolean): String? {
|
||||
var size = filename.length
|
||||
if (size == 0) {
|
||||
return filename
|
||||
}
|
||||
|
||||
val prefix = getPrefixLength(filename)
|
||||
if (prefix < 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
val array = CharArray(size + 2) // +1 for possible extra slash, +2 for arraycopy
|
||||
filename.toCharArray(array, 0, 0, filename.length)
|
||||
|
||||
// fix separators throughout
|
||||
val otherSeparator = if (separator == SYSTEM_SEPARATOR) OTHER_SEPARATOR else SYSTEM_SEPARATOR
|
||||
for (i in array.indices) {
|
||||
if (array[i] == otherSeparator) {
|
||||
array[i] = separator
|
||||
}
|
||||
}
|
||||
|
||||
// add extra separator on the end to simplify code below
|
||||
var lastIsDirectory = true
|
||||
if (array[size - 1] != separator) {
|
||||
array[size++] = separator
|
||||
lastIsDirectory = false
|
||||
}
|
||||
|
||||
// adjoining slashes
|
||||
run {
|
||||
var i = prefix + 1
|
||||
while (i < size) {
|
||||
if (array[i] == separator && array[i - 1] == separator) {
|
||||
System.arraycopy(array, i, array, i - 1, size - i)
|
||||
size--
|
||||
i--
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// dot slash
|
||||
var i = prefix + 1
|
||||
while (i < size) {
|
||||
if (array[i] == separator && array[i - 1] == '.' && (i == prefix + 1 || array[i - 2] == separator)) {
|
||||
if (i == size - 1) {
|
||||
lastIsDirectory = true
|
||||
}
|
||||
System.arraycopy(array, i + 1, array, i - 1, size - i)
|
||||
size -= 2
|
||||
i--
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
i = prefix + 2
|
||||
outer@ while (i < size) {
|
||||
if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && (i == prefix + 2 || array[i - 3] == separator)) {
|
||||
if (i == prefix + 2) {
|
||||
return null
|
||||
}
|
||||
if (i == size - 1) {
|
||||
lastIsDirectory = true
|
||||
}
|
||||
var j: Int
|
||||
j = i - 4
|
||||
while (j >= prefix) {
|
||||
if (array[j] == separator) {
|
||||
// remove b/../ from a/b/../c
|
||||
System.arraycopy(array, i + 1, array, j + 1, size - i)
|
||||
size -= i - j
|
||||
i = j + 1
|
||||
i++
|
||||
continue@outer
|
||||
}
|
||||
j--
|
||||
}
|
||||
// remove a/../ from a/../c
|
||||
System.arraycopy(array, i + 1, array, prefix, size - i)
|
||||
size -= i + 1 - prefix
|
||||
i = prefix + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if (size <= 0) { // should never be less than 0
|
||||
return ""
|
||||
}
|
||||
|
||||
if (size <= prefix) { // should never be less than prefix
|
||||
return String(array, 0, size)
|
||||
}
|
||||
|
||||
return if (lastIsDirectory && keepSeparator) {
|
||||
String(array, 0, size) // keep trailing separator
|
||||
} else String(array, 0, size - 1)
|
||||
// lose trailing separator
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Returns the length of the filename prefix, such as `C:/` or `~/`.
|
||||
*
|
||||
*
|
||||
* This method will handle a file in either Unix or Windows format.
|
||||
*
|
||||
*
|
||||
* The prefix length includes the first slash in the full filename
|
||||
* if applicable. Thus, it is possible that the length returned is greater
|
||||
* than the length of the input string.
|
||||
````
|
||||
Windows:
|
||||
a\b\c.txt --> "" --> relative
|
||||
\a\b\c.txt --> "\" --> current drive absolute
|
||||
C:a\b\c.txt --> "C:" --> drive relative
|
||||
C:\a\b\c.txt --> "C:\" --> absolute
|
||||
\\server\a\b\c.txt --> "\\server\" --> UNC
|
||||
|
||||
Unix:
|
||||
a/b/c.txt --> "" --> relative
|
||||
/a/b/c.txt --> "/" --> absolute
|
||||
~/a/b/c.txt --> "~/" --> current user
|
||||
~ --> "~/" --> current user (slash added)
|
||||
~user/a/b/c.txt --> "~user/" --> named user
|
||||
~user --> "~user/" --> named user (slash added)
|
||||
````
|
||||
*
|
||||
*
|
||||
*
|
||||
* The output will be the same irrespective of the machine that the code is running on.
|
||||
* ie. both Unix and Windows prefixes are matched regardless.
|
||||
*
|
||||
* @param filename the filename to find the prefix in, null returns -1
|
||||
* @return the length of the prefix, -1 if invalid or null
|
||||
*/
|
||||
fun getPrefixLength(filename: String): Int {
|
||||
val len = filename.length
|
||||
if (len == 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
var ch0 = filename[0]
|
||||
if (ch0 == ':') {
|
||||
return -1
|
||||
}
|
||||
|
||||
return if (len == 1) {
|
||||
if (ch0 == '~') {
|
||||
return 2 // return a length greater than the input
|
||||
}
|
||||
if (isSeparator(ch0)) 1 else 0
|
||||
} else {
|
||||
if (ch0 == '~') {
|
||||
var posUnix = filename.indexOf(UNIX_SEPARATOR, 1)
|
||||
var posWin = filename.indexOf(WINDOWS_SEPARATOR, 1)
|
||||
if (posUnix == -1 && posWin == -1) {
|
||||
return len + 1 // return a length greater than the input
|
||||
}
|
||||
posUnix = if (posUnix == -1) posWin else posUnix
|
||||
posWin = if (posWin == -1) posUnix else posWin
|
||||
return Math.min(posUnix, posWin) + 1
|
||||
}
|
||||
val ch1 = filename[1]
|
||||
if (ch1 == ':') {
|
||||
ch0 = ch0.uppercaseChar()
|
||||
if (ch0 >= 'A' && ch0 <= 'Z') {
|
||||
return if (len == 2 || isSeparator(filename[2]) == false) {
|
||||
2
|
||||
} else 3
|
||||
}
|
||||
-1
|
||||
} else if (isSeparator(ch0) && isSeparator(ch1)) {
|
||||
var posUnix = filename.indexOf(UNIX_SEPARATOR, 2)
|
||||
var posWin = filename.indexOf(WINDOWS_SEPARATOR, 2)
|
||||
if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) {
|
||||
return -1
|
||||
}
|
||||
posUnix = if (posUnix == -1) posWin else posUnix
|
||||
posWin = if (posWin == -1) posUnix else posWin
|
||||
Math.min(posUnix, posWin) + 1
|
||||
} else {
|
||||
if (isSeparator(ch0)) 1 else 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/*
|
||||
* FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
|
||||
* http://commons.apache.org/proper/commons-io/
|
||||
* Copyright 2013 ASF
|
||||
* Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck,
|
||||
* Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper,
|
||||
* Jeremias Maerki, Stephen Colebourne
|
||||
*/
|
||||
/**
|
||||
* Checks if the character is a separator.
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if it is a separator character
|
||||
*/
|
||||
private fun isSeparator(ch: Char): Boolean {
|
||||
return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file (text after the last '.')
|
||||
*
|
||||
* @return "" if there is no extension
|
||||
*/
|
||||
fun getExtension(fileName: String): String {
|
||||
val dot = fileName.lastIndexOf('.')
|
||||
return if (dot > -1) {
|
||||
fileName.substring(dot + 1)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of a file that is before the extension (text before the last '.')
|
||||
*
|
||||
* @return non-null
|
||||
*/
|
||||
fun getNameWithoutExtension(fileName: String): String {
|
||||
val dot = fileName.lastIndexOf('.')
|
||||
return if (dot > -1) {
|
||||
fileName.substring(0, dot)
|
||||
} else {
|
||||
fileName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,11 @@ import java.net.URISyntaxException
|
|||
*/
|
||||
@Suppress("unused")
|
||||
object FontUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
/** Default location where all the fonts are stored */
|
||||
@Volatile
|
||||
var FONTS_LOCATION = getProperty(FontUtil::class.java.canonicalName + ".FONTS_LOCATION", "resources/fonts")
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import dorkbox.util.Sys.charToBytes16
|
||||
import dorkbox.util.Sys.concatBytes
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest
|
||||
|
||||
/**
|
||||
* Bouncycastle hashes!
|
||||
*/
|
||||
object HashBouncyUtil {
|
||||
/**
|
||||
* gets the SHA256 hash + SALT of the specified username, as UTF-16
|
||||
*/
|
||||
fun getSha256WithSalt(username: String?, saltBytes: ByteArray): ByteArray? {
|
||||
if (username == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val charToBytes = charToBytes16(username.toCharArray())
|
||||
val userNameWithSalt = concatBytes(charToBytes, saltBytes)
|
||||
|
||||
val sha256 = SHA256Digest()
|
||||
val usernameHashBytes = ByteArray(sha256.digestSize)
|
||||
sha256.update(userNameWithSalt, 0, userNameWithSalt.size)
|
||||
sha256.doFinal(usernameHashBytes, 0)
|
||||
|
||||
return usernameHashBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the SHA256 hash of the specified string, as UTF-16
|
||||
*/
|
||||
fun getSha256(string: String): ByteArray {
|
||||
val charToBytes = charToBytes16(string.toCharArray())
|
||||
|
||||
val sha256 = SHA256Digest()
|
||||
val usernameHashBytes = ByteArray(sha256.digestSize)
|
||||
sha256.update(charToBytes, 0, charToBytes.size)
|
||||
sha256.doFinal(usernameHashBytes, 0)
|
||||
|
||||
return usernameHashBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the SHA256 hash of the specified byte array
|
||||
*/
|
||||
fun getSha256(bytes: ByteArray): ByteArray {
|
||||
val sha256 = SHA256Digest()
|
||||
val hashBytes = ByteArray(sha256.digestSize)
|
||||
sha256.update(bytes, 0, bytes.size)
|
||||
sha256.doFinal(hashBytes, 0)
|
||||
return hashBytes
|
||||
}
|
||||
|
||||
fun getSha256WithSalt(bytes: ByteArray?, saltBytes: ByteArray?): ByteArray? {
|
||||
if (bytes == null || saltBytes == null) {
|
||||
return null
|
||||
}
|
||||
val bytesWithSalt = concatBytes(bytes, saltBytes)
|
||||
val sha256 = SHA256Digest()
|
||||
val usernameHashBytes = ByteArray(sha256.digestSize)
|
||||
sha256.update(bytesWithSalt, 0, bytesWithSalt.size)
|
||||
sha256.doFinal(usernameHashBytes, 0)
|
||||
return usernameHashBytes
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import dorkbox.util.Sys.charToBytes16
|
||||
import dorkbox.util.Sys.concatBytes
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
/**
|
||||
* Java native hashes!
|
||||
*/
|
||||
object HashUtil {
|
||||
private val digestLocal = ThreadLocal.withInitial {
|
||||
try {
|
||||
return@withInitial MessageDigest.getInstance("SHA-256")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw RuntimeException("Unable to initialize hash algorithm. SHA-256 digest doesn't exist?!? (This should not happen")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the SHA256 hash + SALT of the specified username, as UTF-16
|
||||
*/
|
||||
fun getSha256WithSalt(username: String?, saltBytes: ByteArray?): ByteArray? {
|
||||
if (username == null) {
|
||||
return null
|
||||
}
|
||||
val charToBytes = charToBytes16(username.toCharArray())
|
||||
val userNameWithSalt = concatBytes(charToBytes, saltBytes!!)
|
||||
|
||||
val sha256 = digestLocal.get()
|
||||
val usernameHashBytes = ByteArray(sha256.digestLength)
|
||||
sha256.update(userNameWithSalt, 0, userNameWithSalt.size)
|
||||
sha256.digest(usernameHashBytes)
|
||||
|
||||
return usernameHashBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the SHA256 hash of the specified string, as UTF-16
|
||||
*/
|
||||
fun getSha256(string: String): ByteArray {
|
||||
val charToBytes = charToBytes16(string.toCharArray())
|
||||
|
||||
val sha256 = digestLocal.get()
|
||||
val usernameHashBytes = ByteArray(sha256.digestLength)
|
||||
sha256.update(charToBytes, 0, charToBytes.size)
|
||||
sha256.digest(usernameHashBytes)
|
||||
|
||||
return usernameHashBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the SHA256 hash of the specified byte array
|
||||
*/
|
||||
fun getSha256(bytes: ByteArray): ByteArray {
|
||||
val sha256 = digestLocal.get()
|
||||
val hashBytes = ByteArray(sha256.digestLength)
|
||||
sha256.update(bytes, 0, bytes.size)
|
||||
sha256.digest(hashBytes)
|
||||
|
||||
return hashBytes
|
||||
}
|
||||
|
||||
fun getSha256WithSalt(bytes: ByteArray?, saltBytes: ByteArray?): ByteArray? {
|
||||
if (bytes == null || saltBytes == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val bytesWithSalt = concatBytes(bytes, saltBytes)
|
||||
|
||||
val sha256 = digestLocal.get()
|
||||
val usernameHashBytes = ByteArray(sha256.digestLength)
|
||||
sha256.update(bytesWithSalt, 0, bytesWithSalt.size)
|
||||
sha256.digest(usernameHashBytes)
|
||||
|
||||
return usernameHashBytes
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -25,13 +25,13 @@ import javax.imageio.stream.ImageInputStream;
|
|||
|
||||
@SuppressWarnings({"unused", "Duplicates"})
|
||||
// deprecated. use kotlin.
|
||||
@Deprecated
|
||||
public
|
||||
class IO {
|
||||
/**
|
||||
* Convenient close for a Closeable.
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Deprecated
|
||||
public static
|
||||
void close(final Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
|
@ -48,6 +48,7 @@ class IO {
|
|||
* Convenient close for a Closeable.
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Deprecated
|
||||
public static
|
||||
void closeQuietly(final Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
|
@ -61,8 +62,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to the output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
<T extends OutputStream> T copyStream(final InputStream inputStream, final T outputStream) throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
|
@ -79,8 +81,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to the output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
<T extends OutputStream> T copyStream(final ImageInputStream inputStream, final T outputStream) throws IOException {
|
||||
return copyStream(4096, inputStream, outputStream);
|
||||
|
@ -89,8 +92,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to the output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
<T extends OutputStream> T copyStream(final int bufferSize, final ImageInputStream inputStream, final T outputStream) throws IOException {
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
@ -107,8 +111,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to a new output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
ByteArrayOutputStream copyStream(final InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(4096);
|
||||
|
@ -126,8 +131,9 @@ class IO {
|
|||
/**
|
||||
* Copy the contents of the input stream to a new output stream.
|
||||
* <p>
|
||||
* DOES NOT CLOSE THE STEAMS!
|
||||
* DOES NOT CLOSE THE STREAMS!
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
ByteArrayOutputStream copyStream(final ImageInputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(4096);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -145,7 +145,7 @@ class ImageUtil {
|
|||
|
||||
// have to resize the file (and return the new path)
|
||||
|
||||
String extension = FileUtil.INSTANCE.getExtension(fileName);
|
||||
String extension = Sys.INSTANCE.getExtension(fileName);
|
||||
if (extension.isEmpty()) {
|
||||
extension = "png"; // made up
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ class ImageUtil {
|
|||
}
|
||||
else {
|
||||
// suck it out of a URL/Resource (with debugging if necessary)
|
||||
final URL systemResource = LocationResolver.getResource(fileName);
|
||||
final URL systemResource = LocationResolver.Companion.getResource(fileName);
|
||||
image = new ImageIcon(systemResource).getImage();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import org.tukaani.xz.LZMA2Options
|
||||
import org.tukaani.xz.LZMAInputStream
|
||||
import org.tukaani.xz.LZMAOutputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
object LZMA {
|
||||
// https://tukaani.org/xz/java.html
|
||||
@Throws(IOException::class)
|
||||
fun encode(input: InputStream, output: OutputStream) {
|
||||
LZMAOutputStream(output, LZMA2Options(3), true).use { compressionStream ->
|
||||
input.copyTo(compressionStream)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun decode(input: InputStream): ByteArrayOutputStream {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream(8192)
|
||||
LZMAInputStream(input).use { compressedStream -> compressedStream.copyTo(byteArrayOutputStream) }
|
||||
return byteArrayOutputStream
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun decode(input: InputStream, output: OutputStream) {
|
||||
LZMAInputStream(input).use { compressedStream -> compressedStream.copyTo(output) }
|
||||
}
|
||||
}
|
|
@ -222,6 +222,11 @@ class LocationResolver {
|
|||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private val SLASH_PATTERN = Pattern.compile("\\\\")
|
||||
|
||||
private fun log(message: String) {
|
||||
|
@ -280,7 +285,6 @@ class LocationResolver {
|
|||
*
|
||||
* @return the URL for that given resource name
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getResource(resourceName: String): URL? {
|
||||
var resourceName = resourceName
|
||||
try {
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
package dorkbox.util
|
||||
|
||||
object MathUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
/**
|
||||
* Checks to see if the string is an integer
|
||||
*
|
||||
|
|
|
@ -1262,6 +1262,11 @@ open class MersenneTwisterFast {
|
|||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
// Serialization
|
||||
private const val serialVersionUID = -8219700664442619525L // locked as of Version 15
|
||||
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
public interface Message {
|
||||
|
||||
}
|
|
@ -45,6 +45,14 @@ class NamedThreadFactory(
|
|||
constructor(poolNamePrefix: String, group: ThreadGroup, threadPriority: Int, isDaemon: Boolean) : this(poolNamePrefix, group, threadPriority, isDaemon, {})
|
||||
constructor(poolNamePrefix: String, group: ThreadGroup, isDaemon: Boolean, actionOnNewThread: (Thread) -> Unit) : this(poolNamePrefix, group, Thread.NORM_PRIORITY, isDaemon, actionOnNewThread)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
}
|
||||
|
||||
|
||||
|
||||
private val poolId = AtomicInteger()
|
||||
|
||||
|
|
|
@ -26,6 +26,11 @@ import java.security.PrivilegedAction
|
|||
* Loads the specified library, extracting it from the jar, if necessary
|
||||
*/
|
||||
object NativeLoader {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun extractLibrary(sourceFileName: String, destinationDirectory: String?, destinationName: String, version: String?): File {
|
||||
return try {
|
||||
|
|
|
@ -19,6 +19,11 @@ package dorkbox.util
|
|||
* This class uses the MersenneTwisterFast, which is MOSTLY random.
|
||||
*/
|
||||
object RandomUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private val random: FastThreadLocal<MersenneTwisterFast> = object : FastThreadLocal<MersenneTwisterFast>() {
|
||||
override fun initialValue(): MersenneTwisterFast {
|
||||
return MersenneTwisterFast()
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
package dorkbox.util
|
||||
|
||||
object RegExp {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private const val whitespace_chars = ("" /* dummy empty string for homogeneity */ + "\\u0009" // CHARACTER TABULATION
|
||||
+ "\\u000A" // LINE FEED (LF)
|
||||
+ "\\u000B" // LINE TABULATION
|
||||
|
|
|
@ -21,6 +21,11 @@ import java.awt.*
|
|||
* Screen utilities
|
||||
*/
|
||||
object ScreenUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
|
||||
/**
|
||||
* @return the screen bounds for the monitor at a specific point. Will return null if there is no screen at the point
|
||||
|
|
|
@ -26,6 +26,11 @@ import javax.swing.*
|
|||
|
||||
@Suppress("unused")
|
||||
object SwingUtil {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
init {/*
|
||||
* hack workaround for starting the Toolkit thread before any Timer stuff javax.swing.Timer uses the Event Dispatch Thread, which is not
|
||||
* created until the Toolkit thread starts up. Using the Swing Timer before starting this stuff starts up may get unexpected
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import dorkbox.os.OS.LINE_SEPARATOR
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
@ -24,55 +23,22 @@ import java.util.concurrent.*
|
|||
|
||||
@Suppress("unused")
|
||||
object Sys {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = "1.48"
|
||||
|
||||
init {
|
||||
// Add this project to the updates system, which verifies this class + UUID + version information
|
||||
dorkbox.updates.Updates.add(Sys::class.java, "aebbb926aeb144739e9f3cab90ffaa72", version)
|
||||
}
|
||||
|
||||
|
||||
const val KILOBYTE = 1024
|
||||
const val MEGABYTE = 1024 * KILOBYTE
|
||||
const val GIGABYTE = 1024 * MEGABYTE
|
||||
const val TERABYTE = 1024L * GIGABYTE
|
||||
val HEX_CHARS = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
|
||||
fun convertStringToChars(string: String): CharArray {
|
||||
val charArray = string.toCharArray()
|
||||
eraseString(string)
|
||||
return charArray
|
||||
}
|
||||
|
||||
fun eraseString(string: String?) {
|
||||
// You can change the value of the inner char[] using reflection.
|
||||
//
|
||||
// You must be careful to either change it with an array of the same length,
|
||||
// or to also update the count field.
|
||||
//
|
||||
// If you want to be able to use it as an entry in a set or as a value in map,
|
||||
// you will need to recalculate the hash code and set the value of the hashCode field.
|
||||
try {
|
||||
val valueField = String::class.java.getDeclaredField("value")
|
||||
valueField.isAccessible = true
|
||||
val chars = valueField[string] as CharArray
|
||||
Arrays.fill(chars, '*') // asterisk it out in case of GC not picking up the old char array.
|
||||
valueField[string] = CharArray(0) // replace it.
|
||||
|
||||
// set count to 0
|
||||
try {
|
||||
// newer versions of java don't have this field
|
||||
val countField = String::class.java.getDeclaredField("count")
|
||||
countField.isAccessible = true
|
||||
countField[string] = 0
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
// set hash to 0
|
||||
val hashField = String::class.java.getDeclaredField("hash")
|
||||
hashField.isAccessible = true
|
||||
hashField[string] = 0
|
||||
} catch (e: SecurityException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FROM: https://www.cqse.eu/en/blog/string-replace-performance/
|
||||
|
@ -151,22 +117,22 @@ object Sys {
|
|||
fun getTimePretty(nanoSeconds: Long): String {
|
||||
val unit: TimeUnit
|
||||
val text: String
|
||||
if (TimeUnit.DAYS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
if (TimeUnit.DAYS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.DAYS
|
||||
text = "d"
|
||||
} else if (TimeUnit.HOURS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
} else if (TimeUnit.HOURS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.HOURS
|
||||
text = "h"
|
||||
} else if (TimeUnit.MINUTES.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
} else if (TimeUnit.MINUTES.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MINUTES
|
||||
text = "min"
|
||||
} else if (TimeUnit.SECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
text = "m"
|
||||
} else if (TimeUnit.SECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.SECONDS
|
||||
text = "s"
|
||||
} else if (TimeUnit.MILLISECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
} else if (TimeUnit.MILLISECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MILLISECONDS
|
||||
text = "ms"
|
||||
} else if (TimeUnit.MICROSECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
} else if (TimeUnit.MICROSECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MICROSECONDS
|
||||
text = "\u03bcs" // μs
|
||||
} else {
|
||||
|
@ -176,7 +142,16 @@ object Sys {
|
|||
|
||||
// convert the unit into the largest time unit possible (since that is often what makes sense)
|
||||
val value = nanoSeconds.toDouble() / TimeUnit.NANOSECONDS.convert(1, unit)
|
||||
return String.format("%.4g$text", value)
|
||||
|
||||
return if (value < 10) {
|
||||
String.format("%.1g $text", value)
|
||||
} else if (value < 100) {
|
||||
String.format("%.2g $text", value)
|
||||
} else if (value < 1000) {
|
||||
String.format("%.3g $text", value)
|
||||
} else {
|
||||
String.format("%.4g $text", value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,22 +160,22 @@ object Sys {
|
|||
fun getTimePrettyFull(nanoSeconds: Long): String {
|
||||
val unit: TimeUnit
|
||||
var text: String
|
||||
if (TimeUnit.DAYS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
if (TimeUnit.DAYS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.DAYS
|
||||
text = "day"
|
||||
} else if (TimeUnit.HOURS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
} else if (TimeUnit.HOURS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.HOURS
|
||||
text = "hour"
|
||||
} else if (TimeUnit.MINUTES.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
} else if (TimeUnit.MINUTES.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MINUTES
|
||||
text = "minute"
|
||||
} else if (TimeUnit.SECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
} else if (TimeUnit.SECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.SECONDS
|
||||
text = "second"
|
||||
} else if (TimeUnit.MILLISECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
} else if (TimeUnit.MILLISECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MILLISECONDS
|
||||
text = "milli-second"
|
||||
} else if (TimeUnit.MICROSECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0) {
|
||||
} else if (TimeUnit.MICROSECONDS.convert(nanoSeconds, TimeUnit.NANOSECONDS) > 0L) {
|
||||
unit = TimeUnit.MICROSECONDS
|
||||
text = "micro-second"
|
||||
} else {
|
||||
|
@ -213,12 +188,49 @@ object Sys {
|
|||
if (value > 1.0) {
|
||||
text += "s"
|
||||
}
|
||||
return String.format("%.4g $text", value)
|
||||
|
||||
return if (value < 10) {
|
||||
String.format("%.1g $text", value)
|
||||
} else if (value < 100) {
|
||||
String.format("%.2g $text", value)
|
||||
} else if (value < 1000) {
|
||||
String.format("%.3g $text", value)
|
||||
} else {
|
||||
String.format("%.4g $text", value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Throwable> throwException0(t: Throwable) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
throw t as T
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Thrown exception, to bypasses the compiler checks for the checked exception. This uses type erasure to work.
|
||||
*/
|
||||
fun Throwable.unchecked() {
|
||||
throwException0<RuntimeException>(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file (text after the last '.')
|
||||
*
|
||||
* @return "" if there is no extension
|
||||
*/
|
||||
@Deprecated("Use kotlin")
|
||||
fun getExtension(fileName: String): String {
|
||||
val dot = fileName.lastIndexOf('.')
|
||||
return if (dot > -1) {
|
||||
fileName.substring(dot + 1)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the contents of the input stream to a byte array.
|
||||
*/
|
||||
@Deprecated("Use kotlin")
|
||||
@Throws(IOException::class)
|
||||
fun getBytesFromStream(inputStream: InputStream): ByteArray {
|
||||
val baos = ByteArrayOutputStream(8192)
|
||||
|
@ -232,6 +244,7 @@ object Sys {
|
|||
return baos.toByteArray()
|
||||
}
|
||||
|
||||
@Deprecated("Use kotlin")
|
||||
@JvmOverloads
|
||||
fun copyBytes(src: ByteArray, position: Int = 0): ByteArray {
|
||||
val length = src.size - position
|
||||
|
@ -240,7 +253,7 @@ object Sys {
|
|||
return b
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Use kotlin")
|
||||
fun concatBytes(vararg arrayBytes: ByteArray): ByteArray {
|
||||
var length = 0
|
||||
for (bytes in arrayBytes) {
|
||||
|
@ -254,342 +267,46 @@ object Sys {
|
|||
}
|
||||
return concatBytes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this saves the char array in UTF-16 format of bytes
|
||||
*/
|
||||
@JvmStatic
|
||||
fun charToBytes16(text: CharArray): ByteArray {
|
||||
// NOTE: this saves the char array in UTF-16 format of bytes.
|
||||
val bytes = ByteArray(text.size * 2)
|
||||
for (i in text.indices) {
|
||||
bytes[2 * i] = (text[i].code shr 8).toByte()
|
||||
bytes[2 * i + 1] = text[i].code.toByte()
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
/**
|
||||
* Erase the contents of a string, in-memory. This has no effect if the string has been interned.
|
||||
*/
|
||||
fun String.eraseString() {
|
||||
// You can change the value of the inner char[] using reflection.
|
||||
//
|
||||
// You must be careful to either change it with an array of the same length,
|
||||
// or to also update the count field.
|
||||
//
|
||||
// If you want to be able to use it as an entry in a set or as a value in map,
|
||||
// you will need to recalculate the hash code and set the value of the hashCode field.
|
||||
try {
|
||||
val valueField = String::class.java.getDeclaredField("value")
|
||||
valueField.isAccessible = true
|
||||
val chars = valueField[this] as CharArray
|
||||
Arrays.fill(chars, '*') // asterisk it out in case of GC not picking up the old char array.
|
||||
valueField[this] = CharArray(0) // replace it.
|
||||
|
||||
fun intsToBytes(ints: IntArray): ByteArray {
|
||||
val length = ints.size
|
||||
val bytes = ByteArray(length)
|
||||
for (i in 0 until length) {
|
||||
val intValue = ints[i]
|
||||
if (intValue < 0 || intValue > 255) {
|
||||
System.err.println("WARNING: int at index $i($intValue) was not a valid byte value (0-255)")
|
||||
return ByteArray(length)
|
||||
}
|
||||
bytes[i] = intValue.toByte()
|
||||
// set count to 0
|
||||
try {
|
||||
// newer versions of java don't have this field
|
||||
val countField = String::class.java.getDeclaredField("count")
|
||||
countField.isAccessible = true
|
||||
countField[this] = 0
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
fun charToBytesRaw(chars: CharArray): ByteArray {
|
||||
val length = chars.size
|
||||
val bytes = ByteArray(length)
|
||||
for (i in 0 until length) {
|
||||
val charValue = chars[i]
|
||||
bytes[i] = charValue.code.toByte()
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
fun bytesToInts(bytes: ByteArray, startPosition: Int, length: Int): IntArray {
|
||||
val ints = IntArray(length)
|
||||
val endPosition = startPosition + length
|
||||
for (i in startPosition until endPosition) {
|
||||
ints[i] = bytes[i].toInt() and 0xFF
|
||||
}
|
||||
return ints
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun bytesToHex(bytes: ByteArray, startPosition: Int = 0, length: Int = bytes.size, padding: Boolean = false): String {
|
||||
val endPosition = startPosition + length
|
||||
return if (padding) {
|
||||
val hexString = CharArray(3 * length)
|
||||
var j = 0
|
||||
for (i in startPosition until endPosition) {
|
||||
hexString[j++] = HEX_CHARS[bytes[i].toInt() and 0xF0 shr 4]
|
||||
hexString[j++] = HEX_CHARS[bytes[i].toInt() and 0x0F]
|
||||
hexString[j++] = ' '
|
||||
}
|
||||
String(hexString)
|
||||
} else {
|
||||
val hexString = CharArray(2 * length)
|
||||
var j = 0
|
||||
for (i in startPosition until endPosition) {
|
||||
hexString[j++] = HEX_CHARS[bytes[i].toInt() and 0xF0 shr 4]
|
||||
hexString[j++] = HEX_CHARS[bytes[i].toInt() and 0x0F]
|
||||
}
|
||||
String(hexString)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ASCII character representing a hexadecimal
|
||||
* value into its integer equivalent.
|
||||
*/
|
||||
fun hexByteToInt(b: Byte): Int {
|
||||
return when (b.toInt()) {
|
||||
'0'.code -> 0
|
||||
'1'.code -> 1
|
||||
'2'.code -> 2
|
||||
'3'.code -> 3
|
||||
'4'.code -> 4
|
||||
'5'.code -> 5
|
||||
'6'.code -> 6
|
||||
'7'.code -> 7
|
||||
'8'.code -> 8
|
||||
'9'.code -> 9
|
||||
'A'.code, 'a'.code -> 10
|
||||
'B'.code, 'b'.code -> 11
|
||||
'C'.code, 'c'.code -> 12
|
||||
'D'.code, 'd'.code -> 13
|
||||
'E'.code, 'e'.code -> 14
|
||||
'F'.code, 'f'.code -> 15
|
||||
else -> throw IllegalArgumentException("Error decoding byte")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ASCII character representing a hexadecimal
|
||||
* value into its integer equivalent.
|
||||
*/
|
||||
fun hexCharToInt(b: Char): Int {
|
||||
return when (b) {
|
||||
'0' -> 0
|
||||
'1' -> 1
|
||||
'2' -> 2
|
||||
'3' -> 3
|
||||
'4' -> 4
|
||||
'5' -> 5
|
||||
'6' -> 6
|
||||
'7' -> 7
|
||||
'8' -> 8
|
||||
'9' -> 9
|
||||
'A', 'a' -> 10
|
||||
'B', 'b' -> 11
|
||||
'C', 'c' -> 12
|
||||
'D', 'd' -> 13
|
||||
'E', 'e' -> 14
|
||||
'F', 'f' -> 15
|
||||
else -> throw IllegalArgumentException("Error decoding byte")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A 4-digit hex result.
|
||||
*/
|
||||
fun hex4(c: Char, sb: StringBuilder) {
|
||||
sb.append(HEX_CHARS[c.code and 0xF000 shr 12])
|
||||
sb.append(HEX_CHARS[c.code and 0x0F00 shr 8])
|
||||
sb.append(HEX_CHARS[c.code and 0x00F0 shr 4])
|
||||
sb.append(HEX_CHARS[c.code and 0x000F])
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the byte array as a series of
|
||||
* hexadecimal characters.
|
||||
*
|
||||
* @param bytes byte array to convert
|
||||
* @return a string representation of the byte array as a series of
|
||||
* hexadecimal characters
|
||||
*/
|
||||
fun toHexString(bytes: ByteArray): String {
|
||||
val hexString = CharArray(2 * bytes.size)
|
||||
var j = 0
|
||||
for (i in bytes.indices) {
|
||||
hexString[j++] = HEX_CHARS[bytes[i].toInt() and 0xF0 shr 4]
|
||||
hexString[j++] = HEX_CHARS[bytes[i].toInt() and 0x0F]
|
||||
}
|
||||
return String(hexString)
|
||||
}
|
||||
|
||||
/**
|
||||
* from netty 4.1, apache 2.0, https://netty.io
|
||||
*/
|
||||
fun hexToByte(s: CharSequence, pos: Int): Byte {
|
||||
val hi = hexCharToInt(s[pos])
|
||||
val lo = hexCharToInt(s[pos + 1])
|
||||
require(!(hi == -1 || lo == -1)) {
|
||||
String.format(
|
||||
"invalid hex byte '%s' at index %d of '%s'", s.subSequence(pos, pos + 2), pos, s
|
||||
)
|
||||
}
|
||||
return ((hi shl 4) + lo).toByte()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a string with [hex dump](http://en.wikipedia.org/wiki/Hex_dump)
|
||||
*
|
||||
* @param hex a [CharSequence] which contains the hex dump
|
||||
*/
|
||||
fun hexToBytes(hex: CharSequence): ByteArray {
|
||||
return hexToBytes(hex, 0, hex.length)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes part of a string with [hex dump](http://en.wikipedia.org/wiki/Hex_dump)
|
||||
*
|
||||
* from netty 4.1, apache 2.0, https://netty.io
|
||||
*
|
||||
* @param hexDump a [CharSequence] which contains the hex dump
|
||||
* @param fromIndex start of hex dump in `hexDump`
|
||||
* @param length hex string length
|
||||
*/
|
||||
fun hexToBytes(hexDump: CharSequence, fromIndex: Int, length: Int): ByteArray {
|
||||
require(!(length < 0 || length and 1 != 0)) { "length: $length" }
|
||||
if (length == 0) {
|
||||
return ByteArray(0)
|
||||
}
|
||||
val bytes = ByteArray(length ushr 1)
|
||||
var i = 0
|
||||
while (i < length) {
|
||||
bytes[i ushr 1] = hexToByte(hexDump, fromIndex + i)
|
||||
i += 2
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* XOR two byte arrays together, and save result in originalArray
|
||||
*
|
||||
* @param originalArray this is the base of the XOR operation.
|
||||
* @param keyArray this is XOR'd into the original array, repeats if necessary.
|
||||
*/
|
||||
fun xorArrays(originalArray: ByteArray, keyArray: ByteArray) {
|
||||
var keyIndex = 0
|
||||
val keyLength = keyArray.size
|
||||
for (i in originalArray.indices) {
|
||||
//XOR the data and start over if necessary
|
||||
originalArray[i] = (originalArray[i].toInt() xor keyArray[keyIndex++ % keyLength].toInt()).toByte()
|
||||
}
|
||||
}
|
||||
|
||||
fun encodeStringArray(array: List<String>): ByteArray {
|
||||
var length = 0
|
||||
for (s in array) {
|
||||
val bytes = s.toByteArray()
|
||||
length += bytes.size
|
||||
}
|
||||
if (length == 0) {
|
||||
return ByteArray(0)
|
||||
}
|
||||
val bytes = ByteArray(length + array.size)
|
||||
length = 0
|
||||
for (s in array) {
|
||||
val sBytes = s.toByteArray()
|
||||
System.arraycopy(sBytes, 0, bytes, length, sBytes.size)
|
||||
length += sBytes.size
|
||||
bytes[length++] = 0x01.toByte()
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
fun decodeStringArray(bytes: ByteArray): ArrayList<String> {
|
||||
val length = bytes.size
|
||||
var position = 0
|
||||
val token = 0x01.toByte()
|
||||
val list = ArrayList<String>(0)
|
||||
var last = 0
|
||||
while (last + position < length) {
|
||||
val b = bytes[last + position++]
|
||||
if (b == token) {
|
||||
val xx = ByteArray(position - 1)
|
||||
System.arraycopy(bytes, last, xx, 0, position - 1)
|
||||
list.add(String(xx))
|
||||
last += position
|
||||
position = 0
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun printArrayRaw(bytes: ByteArray, lineLength: Int = 0): String {
|
||||
return if (lineLength > 0) {
|
||||
val length = bytes.size
|
||||
val comma = length - 1
|
||||
val builder = StringBuilder(length + length / lineLength)
|
||||
for (i in 0 until length) {
|
||||
builder.append(bytes[i].toInt())
|
||||
if (i < comma) {
|
||||
builder.append(",")
|
||||
}
|
||||
if (i > 0 && i % lineLength == 0) {
|
||||
builder.append(LINE_SEPARATOR)
|
||||
}
|
||||
}
|
||||
builder.toString()
|
||||
} else {
|
||||
val length = bytes.size
|
||||
val comma = length - 1
|
||||
val builder = StringBuilder(length + length)
|
||||
for (i in 0 until length) {
|
||||
builder.append(bytes[i].toInt())
|
||||
if (i < comma) {
|
||||
builder.append(",")
|
||||
}
|
||||
}
|
||||
builder.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun printArray(bytes: ByteArray, length: Int = bytes.size, includeByteCount: Boolean = true) {
|
||||
printArray(bytes, 0, length, includeByteCount, 40, null)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun printArray(
|
||||
bytes: ByteArray,
|
||||
inputOffset: Int,
|
||||
length: Int,
|
||||
includeByteCount: Boolean,
|
||||
lineLength: Int = 40,
|
||||
header: String? = null
|
||||
) {
|
||||
val comma = length - 1
|
||||
var builderLength = length + comma + 2
|
||||
if (includeByteCount) {
|
||||
builderLength += 7 + Integer.toString(length).length
|
||||
}
|
||||
if (lineLength > 0) {
|
||||
builderLength += length / lineLength
|
||||
}
|
||||
if (header != null) {
|
||||
builderLength += header.length + 2
|
||||
}
|
||||
val builder = StringBuilder(builderLength)
|
||||
if (header != null) {
|
||||
builder.append(header).append(LINE_SEPARATOR)
|
||||
}
|
||||
if (includeByteCount) {
|
||||
builder.append("Bytes: ").append(length).append(LINE_SEPARATOR)
|
||||
}
|
||||
builder.append("{")
|
||||
for (i in inputOffset until length) {
|
||||
builder.append(bytes[i].toInt())
|
||||
if (i < comma) {
|
||||
builder.append(",")
|
||||
}
|
||||
if (i > inputOffset && lineLength > 0 && i % lineLength == 0) {
|
||||
builder.append(LINE_SEPARATOR)
|
||||
}
|
||||
}
|
||||
builder.append("}")
|
||||
System.err.println(builder.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises an exception, but bypasses the compiler checks for the checked exception. This uses type erasure to work
|
||||
*/
|
||||
fun throwException(t: Throwable) {
|
||||
throwException0<RuntimeException>(t)
|
||||
}
|
||||
|
||||
private fun <T : Throwable> throwException0(t: Throwable) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
throw t as T
|
||||
// set hash to 0
|
||||
val hashField = String::class.java.getDeclaredField("hash")
|
||||
hashField.isAccessible = true
|
||||
hashField[this] = 0
|
||||
} catch (e: SecurityException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,199 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.classes;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.Objects;
|
||||
|
||||
import net.jodah.typetools.TypeResolver;
|
||||
|
||||
public final
|
||||
class ClassHelper {
|
||||
|
||||
/**
|
||||
* Retrieves the generic type parameter for the PARENT (super) class of the specified class or lambda expression.
|
||||
*
|
||||
* Because of how type erasure works in java, this will work on lambda expressions and ONLY parent/super classes.
|
||||
*
|
||||
* @param genericTypeClass this class is what you are looking for
|
||||
* @param classToCheck class to actually get the parameter from
|
||||
* @param genericParameterToGet 0-based index of parameter as class to get
|
||||
*
|
||||
* @return null if the generic type could not be found.
|
||||
*/
|
||||
@SuppressWarnings({"StatementWithEmptyBody", "UnnecessaryLocalVariable"})
|
||||
public static
|
||||
Class<?> getGenericParameterAsClassForSuperClass(Class<?> genericTypeClass, Class<?> classToCheck, int genericParameterToGet) {
|
||||
Class<?> loopClassCheck = classToCheck;
|
||||
|
||||
// this will ALWAYS return something, if it is unknown, it will return TypeResolver.Unknown.class
|
||||
Class<?>[] classes = TypeResolver.resolveRawArguments(genericTypeClass, loopClassCheck);
|
||||
if (classes.length > genericParameterToGet && classes[genericParameterToGet] != TypeResolver.Unknown.class) {
|
||||
return classes[genericParameterToGet];
|
||||
}
|
||||
|
||||
// case of multiple inheritance, we are trying to get the first available generic info
|
||||
// don't check for Object.class (this is where superclass is null)
|
||||
while (loopClassCheck != Object.class) {
|
||||
// check to see if we have what we are looking for on our CURRENT class
|
||||
Type superClassGeneric = loopClassCheck.getGenericSuperclass();
|
||||
|
||||
classes = TypeResolver.resolveRawArguments(superClassGeneric, loopClassCheck);
|
||||
if (classes.length > genericParameterToGet) {
|
||||
Class<?> aClass = classes[genericParameterToGet];
|
||||
if (aClass != TypeResolver.Unknown.class) {
|
||||
return classes[genericParameterToGet];
|
||||
}
|
||||
}
|
||||
|
||||
// NO MATCH, so walk up.
|
||||
loopClassCheck = loopClassCheck.getSuperclass();
|
||||
}
|
||||
|
||||
// NOTHING! now check interfaces!
|
||||
loopClassCheck = classToCheck;
|
||||
while (loopClassCheck != Object.class) {
|
||||
// check to see if we have what we are looking for on our CURRENT class interfaces
|
||||
Type[] genericInterfaces = loopClassCheck.getGenericInterfaces();
|
||||
|
||||
for (Type genericInterface : genericInterfaces) {
|
||||
classes = TypeResolver.resolveRawArguments(genericInterface, loopClassCheck);
|
||||
if (classes.length > genericParameterToGet) {
|
||||
Class<?> aClass = classes[genericParameterToGet];
|
||||
if (aClass != TypeResolver.Unknown.class) {
|
||||
return aClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NO MATCH, so walk up.
|
||||
loopClassCheck = loopClassCheck.getSuperclass();
|
||||
}
|
||||
|
||||
// couldn't find it.
|
||||
return null;
|
||||
}
|
||||
|
||||
// from: https://github.com/square/retrofit/blob/108fe23964b986107aed352ba467cd2007d15208/retrofit/src/main/java/retrofit2/Utils.java
|
||||
public static Class<?> getRawType(Type type) {
|
||||
Objects.requireNonNull(type, "type == null");
|
||||
|
||||
if (type instanceof Class<?>) {
|
||||
// Type is a normal class.
|
||||
return (Class<?>) type;
|
||||
}
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
|
||||
// I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but
|
||||
// suspects some pathological case related to nested classes exists.
|
||||
Type rawType = parameterizedType.getRawType();
|
||||
if (!(rawType instanceof Class)) throw new IllegalArgumentException();
|
||||
return (Class<?>) rawType;
|
||||
}
|
||||
if (type instanceof GenericArrayType) {
|
||||
Type componentType = ((GenericArrayType) type).getGenericComponentType();
|
||||
return Array.newInstance(getRawType(componentType), 0).getClass();
|
||||
}
|
||||
if (type instanceof TypeVariable) {
|
||||
// We could use the variable's bounds, but that won't work if there are multiple. Having a raw
|
||||
// type that's more general than necessary is okay.
|
||||
return Object.class;
|
||||
}
|
||||
if (type instanceof WildcardType) {
|
||||
return getRawType(((WildcardType) type).getUpperBounds()[0]);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Expected a Class, ParameterizedType, or "
|
||||
+ "GenericArrayType, but <"
|
||||
+ type
|
||||
+ "> is of type "
|
||||
+ type.getClass().getName());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check to see if clazz or interface directly has one of the interfaces defined by requiredClass
|
||||
* <p/>
|
||||
* If the class DOES NOT directly have the interface it will fail.
|
||||
*/
|
||||
public static
|
||||
boolean hasInterface(Class<?> requiredClass, Class<?> clazz) {
|
||||
if (requiredClass == clazz) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Class<?>[] interfaces = clazz.getInterfaces();
|
||||
for (Class<?> iface : interfaces) {
|
||||
if (iface == requiredClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// now walk up to see if we can find it.
|
||||
for (Class<?> iface : interfaces) {
|
||||
boolean b = hasInterface(requiredClass, iface);
|
||||
if (b) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
// nothing, so now we check the PARENT of this class
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
|
||||
// case of multiple inheritance, we are trying to get the first available generic info
|
||||
// don't check for Object.class (this is where superclass is null)
|
||||
while (superClass != null && superClass != Object.class) {
|
||||
// check to see if we have what we are looking for on our CURRENT class
|
||||
if (hasInterface(requiredClass, superClass)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// NO MATCH, so walk up.
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
|
||||
// if we don't find it.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the clazz is a subclass of a parent class.
|
||||
*/
|
||||
@SuppressWarnings("SimplifiableIfStatement")
|
||||
public static
|
||||
boolean hasParentClass(Class<?> parentClazz, Class<?> clazz) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (parentClazz == superClass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (superClass != null && superClass != Object.class) {
|
||||
return hasParentClass(parentClazz, superClass);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private
|
||||
ClassHelper() {
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.classes;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
import dorkbox.collections.IdentityMap;
|
||||
|
||||
/**
|
||||
* @author dorkbox
|
||||
* Date: 4/1/15
|
||||
*/
|
||||
public final
|
||||
class ClassHierarchy {
|
||||
|
||||
private volatile IdentityMap<Class<?>, Class<?>> arrayCache;
|
||||
private volatile IdentityMap<Class<?>, Class<?>[]> superClassesCache;
|
||||
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<ClassHierarchy, IdentityMap> arrayREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(ClassHierarchy.class,
|
||||
IdentityMap.class,
|
||||
"arrayCache");
|
||||
|
||||
private static final AtomicReferenceFieldUpdater<ClassHierarchy, IdentityMap> superClassesREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(ClassHierarchy.class,
|
||||
IdentityMap.class,
|
||||
"superClassesCache");
|
||||
|
||||
/**
|
||||
* These data structures are never reset because the class hierarchy doesn't change at runtime. This class uses the "single writer
|
||||
* principle" for storing data, EVEN THOUGH it's not accessed by a single writer. This DOES NOT MATTER because duplicates DO NOT matter
|
||||
*/
|
||||
public
|
||||
ClassHierarchy(float loadFactor) {
|
||||
this.arrayCache = new IdentityMap<Class<?>, Class<?>>(32, loadFactor);
|
||||
this.superClassesCache = new IdentityMap<Class<?>, Class<?>[]>(32, loadFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* will return the class + parent classes as an array.
|
||||
* if parameter clazz is of type array, then the super classes are of array type as well
|
||||
* <p>
|
||||
* race conditions will result in DUPLICATE answers, which we don't care if happens
|
||||
* never returns null
|
||||
* never reset (class hierarchy never changes during runtime)
|
||||
*/
|
||||
public
|
||||
Class<?>[] getClassAndSuperClasses(final Class<?> clazz) {
|
||||
// access a snapshot of the subscriptions (single-writer-principle)
|
||||
final IdentityMap<Class<?>, Class<?>[]> cache = cast(superClassesREF.get(this));
|
||||
|
||||
Class<?>[] classes = cache.get(clazz);
|
||||
|
||||
// duplicates DO NOT MATTER
|
||||
if (classes == null) {
|
||||
// publish all super types of class
|
||||
final Iterator<Class<?>> superTypesIterator = getSuperTypes(clazz);
|
||||
final ArrayList<Class<?>> newList = new ArrayList<Class<?>>(16);
|
||||
|
||||
Class<?> c;
|
||||
final boolean isArray = clazz.isArray();
|
||||
|
||||
// have to add the original class to the front of the list
|
||||
newList.add(clazz);
|
||||
|
||||
if (isArray) {
|
||||
// super-types for an array ALSO must be an array.
|
||||
while (superTypesIterator.hasNext()) {
|
||||
c = superTypesIterator.next();
|
||||
c = getArrayClass(c);
|
||||
|
||||
if (c != clazz) {
|
||||
newList.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (superTypesIterator.hasNext()) {
|
||||
c = superTypesIterator.next();
|
||||
|
||||
if (c != clazz) {
|
||||
newList.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classes = new Class<?>[newList.size()];
|
||||
newList.toArray(classes);
|
||||
cache.put(clazz, classes);
|
||||
|
||||
// save this snapshot back to the original (single writer principle)
|
||||
superClassesREF.lazySet(this, cache);
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* race conditions will result in DUPLICATE answers, which we don't care if happens
|
||||
* never returns null
|
||||
* never resets (class hierarchy never changes during runtime)
|
||||
*
|
||||
* https://bugs.openjdk.java.net/browse/JDK-6525802 (fixed this in 2007, so Array.newInstance is just as fast (via intrinsics) new [])
|
||||
* Cache is in place to keep GC down.
|
||||
*/
|
||||
public
|
||||
Class<?> getArrayClass(final Class<?> c) {
|
||||
// access a snapshot of the subscriptions (single-writer-principle)
|
||||
final IdentityMap<Class<?>, Class<?>> cache = cast(arrayREF.get(this));
|
||||
|
||||
Class<?> clazz = cache.get(c);
|
||||
|
||||
if (clazz == null) {
|
||||
// messy, but the ONLY way to do it. Array super types are also arrays
|
||||
final Object[] newInstance = (Object[]) Array.newInstance(c, 0);
|
||||
clazz = newInstance.getClass();
|
||||
cache.put(c, clazz);
|
||||
|
||||
// save this snapshot back to the original (single writer principle)
|
||||
arrayREF.lazySet(this, cache);
|
||||
}
|
||||
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all directly and indirectly related super types (classes and interfaces) of a given class.
|
||||
*
|
||||
* @param from The root class to start with
|
||||
* @return An array of classes, each representing a super type of the root class
|
||||
*/
|
||||
public static
|
||||
Iterator<Class<?>> getSuperTypes(Class<?> from) {
|
||||
// This must be a 'set' because there can be duplicates, depending on the object hierarchy
|
||||
final IdentityMap<Class<?>, Boolean> superclasses = new IdentityMap<Class<?>, Boolean>();
|
||||
collectInterfaces(from, superclasses);
|
||||
|
||||
while (!from.equals(Object.class) && !from.isInterface()) {
|
||||
superclasses.put(from.getSuperclass(), Boolean.TRUE);
|
||||
from = from.getSuperclass();
|
||||
collectInterfaces(from, superclasses);
|
||||
}
|
||||
|
||||
return superclasses.keys();
|
||||
}
|
||||
|
||||
private static
|
||||
void collectInterfaces(Class<?> from, IdentityMap<Class<?>, Boolean> accumulator) {
|
||||
for (Class<?> intface : from.getInterfaces()) {
|
||||
accumulator.put(intface, Boolean.TRUE);
|
||||
collectInterfaces(intface, accumulator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears the caches, should only be called on shutdown
|
||||
*/
|
||||
public
|
||||
void shutdown() {
|
||||
this.arrayCache.clear();
|
||||
this.superClassesCache.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static
|
||||
<T> T cast(Object obj) {
|
||||
return (T) obj;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.classes;
|
||||
|
||||
public abstract class ClassResolver {
|
||||
/**
|
||||
* A helper class to get the call context. It subclasses SecurityManager to make getClassContext() accessible. An instance of
|
||||
* CallerResolver only needs to be created, not installed as an actual security manager.
|
||||
*/
|
||||
private static final class CallerResolver extends SecurityManager {
|
||||
@Override
|
||||
protected Class<?>[] getClassContext() {
|
||||
return super.getClassContext();
|
||||
}
|
||||
}
|
||||
|
||||
private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if this class is redesigned
|
||||
private static final CallerResolver CALLER_RESOLVER;
|
||||
|
||||
static {
|
||||
try {
|
||||
// This can fail if the current SecurityManager does not allow
|
||||
// RuntimePermission ("createSecurityManager"):
|
||||
CALLER_RESOLVER = new CallerResolver();
|
||||
} catch (SecurityException se) {
|
||||
throw new RuntimeException("ClassLoaderResolver: could not create CallerResolver: " + se);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes into the current method call context with a given offset.
|
||||
*/
|
||||
public static Class<?> getCallerClass(final int callerOffset) {
|
||||
return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET + callerOffset];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012 Benjamin Diedrichsen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.classes;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dorkbox.collections.IdentityMap;
|
||||
|
||||
/**
|
||||
* @author bennidi
|
||||
* Date: 2/16/12
|
||||
* Time: 12:14 PM
|
||||
* @author dorkbox
|
||||
* Date: 2/2/15
|
||||
*/
|
||||
public final
|
||||
class ReflectionUtils {
|
||||
|
||||
private static final Method[] EMPTY_METHODS = new Method[0];
|
||||
|
||||
private
|
||||
ReflectionUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get methods annotated with the specified annotation.
|
||||
*
|
||||
* @param target the class that you are looking for the methods on
|
||||
* @param annotationClass the annotations that define the method you are looking for
|
||||
* @param <A> the annotation type
|
||||
*
|
||||
* @return the array of methods that match the target + annotation
|
||||
*/
|
||||
public static
|
||||
<A extends Annotation> Method[] getMethods(Class<?> target, Class<A> annotationClass) {
|
||||
ArrayList<Method> methods = new ArrayList<Method>();
|
||||
|
||||
getMethods(target, annotationClass, methods);
|
||||
return methods.toArray(EMPTY_METHODS);
|
||||
}
|
||||
|
||||
private static
|
||||
<A extends Annotation> void getMethods(Class<?> target, Class<A> annotationClass, ArrayList<Method> methods) {
|
||||
try {
|
||||
for (Method method : target.getDeclaredMethods()) {
|
||||
if (getAnnotation(method, annotationClass) != null) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// recursively go until root
|
||||
if (!target.equals(Object.class)) {
|
||||
getMethods(target.getSuperclass(), annotationClass, methods);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the class hierarchy upwards, starting at the given subclass, looking
|
||||
* for an override of the given methods -> finds the bottom most override of the given
|
||||
* method if any exists
|
||||
*/
|
||||
public static
|
||||
Method getOverridingMethod(final Method overridingMethod, final Class<?> subclass) {
|
||||
Class<?> current = subclass;
|
||||
while (!current.equals(overridingMethod.getDeclaringClass())) {
|
||||
try {
|
||||
return current.getDeclaredMethod(overridingMethod.getName(), overridingMethod.getParameterTypes());
|
||||
} catch (NoSuchMethodException e) {
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
boolean containsOverridingMethod(final Method[] allMethods, final Method methodToCheck) {
|
||||
final int length = allMethods.length;
|
||||
Method method;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
method = allMethods[i];
|
||||
|
||||
if (isOverriddenBy(methodToCheck, method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for an Annotation of the given type on the class. Supports meta annotations.
|
||||
*
|
||||
* @param from AnnotatedElement (class, method...)
|
||||
* @param annotationType Annotation class to look for.
|
||||
* @param <A> Class of annotation type
|
||||
* @return Annotation instance or null
|
||||
*/
|
||||
private static
|
||||
<A extends Annotation> A getAnnotation(AnnotatedElement from, Class<A> annotationType, IdentityMap<AnnotatedElement, Boolean> visited) {
|
||||
if (visited.containsKey(from)) {
|
||||
return null;
|
||||
}
|
||||
visited.put(from, Boolean.TRUE);
|
||||
A ann = from.getAnnotation(annotationType);
|
||||
if (ann != null) {
|
||||
return ann;
|
||||
}
|
||||
for (Annotation metaAnn : from.getAnnotations()) {
|
||||
ann = getAnnotation(metaAnn.annotationType(), annotationType, visited);
|
||||
if (ann != null) {
|
||||
return ann;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static
|
||||
<A extends Annotation> A getAnnotation(AnnotatedElement from, Class<A> annotationType) {
|
||||
return getAnnotation(from, annotationType, new IdentityMap<AnnotatedElement, Boolean>());
|
||||
}
|
||||
|
||||
|
||||
private static
|
||||
boolean isOverriddenBy(final Method superclassMethod, final Method subclassMethod) {
|
||||
// if the declaring classes are the same or the subclass method is not defined in the subclass
|
||||
// hierarchy of the given superclass method or the method names are not the same then
|
||||
// subclassMethod does not override superclassMethod
|
||||
if (superclassMethod.getDeclaringClass().equals(subclassMethod.getDeclaringClass()) ||
|
||||
!superclassMethod.getDeclaringClass().isAssignableFrom(subclassMethod.getDeclaringClass()) ||
|
||||
!superclassMethod.getName().equals(subclassMethod.getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Class<?>[] superClassMethodParameters = superclassMethod.getParameterTypes();
|
||||
final Class<?>[] subClassMethodParameters = subclassMethod.getParameterTypes();
|
||||
|
||||
// method must specify the same number of parameters
|
||||
//the parameters must occur in the exact same order
|
||||
for (int i = 0; i < subClassMethodParameters.length; i++) {
|
||||
if (!superClassMethodParameters[i].equals(subClassMethodParameters[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.classes;
|
|
@ -1,801 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Damien Miller <djm@mindrot.org>
|
||||
*
|
||||
* GWT modified version.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* BCrypt implements OpenBSD-style Blowfish password hashing using
|
||||
* the scheme described in "A Future-Adaptable Password Scheme" by
|
||||
* Niels Provos and David Mazieres.
|
||||
* <p>
|
||||
* This password hashing system tries to thwart off-line password
|
||||
* cracking using a computationally-intensive hashing algorithm,
|
||||
* based on Bruce Schneier's Blowfish cipher. The work factor of
|
||||
* the algorithm is parameterised, so it can be increased as
|
||||
* computers get faster.
|
||||
* <p>
|
||||
* Usage is really simple. To hash a password for the first time,
|
||||
* call the hashpw method with a random salt, like this:
|
||||
* <p>
|
||||
* <code>
|
||||
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br />
|
||||
* </code>
|
||||
* <p>
|
||||
* To check whether a plaintext password matches one that has been
|
||||
* hashed previously, use the checkpw method:
|
||||
* <p>
|
||||
* <code>
|
||||
* if (BCrypt.checkpw(candidate_password, stored_hash))<br />
|
||||
* System.out.println("It matches");<br />
|
||||
* else<br />
|
||||
* System.out.println("It does not match");<br />
|
||||
* </code>
|
||||
* <p>
|
||||
* The gensalt() method takes an optional parameter (log_rounds)
|
||||
* that determines the computational complexity of the hashing:
|
||||
* <p>
|
||||
* <code>
|
||||
* String strong_salt = BCrypt.gensalt(10)<br />
|
||||
* String stronger_salt = BCrypt.gensalt(12)<br />
|
||||
* </code>
|
||||
* <p>
|
||||
* The amount of work increases exponentially (2**log_rounds), so
|
||||
* each increment is twice as much work. The default log_rounds is
|
||||
* 10, and the valid range is 4 to 31.
|
||||
*
|
||||
* @author Damien Miller
|
||||
* @version 0.2
|
||||
*/
|
||||
public class BCrypt {
|
||||
// BCrypt parameters
|
||||
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
|
||||
private static final int BCRYPT_SALT_LEN = 16;
|
||||
|
||||
// Blowfish parameters
|
||||
private static final int BLOWFISH_NUM_ROUNDS = 16;
|
||||
|
||||
// Initial contents of key schedule
|
||||
private static final int P_orig[] = {
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
||||
0x9216d5d9, 0x8979fb1b
|
||||
};
|
||||
private static final int S_orig[] = {
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
||||
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
|
||||
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
|
||||
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
|
||||
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
|
||||
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
|
||||
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
|
||||
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
|
||||
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
|
||||
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
|
||||
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
|
||||
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
|
||||
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
|
||||
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
|
||||
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
|
||||
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
|
||||
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
|
||||
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
|
||||
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
|
||||
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
|
||||
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
|
||||
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
|
||||
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
|
||||
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
|
||||
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
|
||||
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
|
||||
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
|
||||
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
|
||||
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
|
||||
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
|
||||
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
|
||||
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
|
||||
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
|
||||
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
|
||||
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
|
||||
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
|
||||
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
|
||||
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
|
||||
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
|
||||
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
|
||||
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
|
||||
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
|
||||
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
|
||||
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
|
||||
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
|
||||
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
|
||||
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
|
||||
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
|
||||
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
|
||||
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
|
||||
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
|
||||
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
|
||||
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
|
||||
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
|
||||
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
|
||||
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
|
||||
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
|
||||
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
|
||||
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
|
||||
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
|
||||
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
|
||||
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
|
||||
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
|
||||
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
|
||||
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
|
||||
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
|
||||
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
|
||||
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
|
||||
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
|
||||
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
|
||||
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
|
||||
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
|
||||
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
|
||||
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
|
||||
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
|
||||
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
|
||||
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
|
||||
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
|
||||
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
|
||||
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
|
||||
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
|
||||
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
|
||||
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
|
||||
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
|
||||
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
|
||||
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
|
||||
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
|
||||
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
|
||||
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
|
||||
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
|
||||
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
|
||||
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
|
||||
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
|
||||
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
|
||||
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
|
||||
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
|
||||
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
|
||||
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
|
||||
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
|
||||
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
|
||||
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
|
||||
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
|
||||
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
|
||||
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
|
||||
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
|
||||
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
|
||||
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
|
||||
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
|
||||
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
|
||||
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
|
||||
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
|
||||
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
|
||||
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
|
||||
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
|
||||
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
|
||||
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
|
||||
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
|
||||
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
|
||||
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
|
||||
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
|
||||
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
|
||||
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
|
||||
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
|
||||
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
|
||||
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
|
||||
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
|
||||
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
|
||||
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
|
||||
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
|
||||
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
|
||||
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
|
||||
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
|
||||
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
|
||||
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
|
||||
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
|
||||
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
|
||||
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
|
||||
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
|
||||
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
|
||||
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
|
||||
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
|
||||
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
|
||||
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
|
||||
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
|
||||
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
|
||||
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
|
||||
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
|
||||
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
|
||||
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
|
||||
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
|
||||
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
|
||||
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
|
||||
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
|
||||
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
|
||||
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
|
||||
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
|
||||
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
|
||||
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
|
||||
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
|
||||
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
|
||||
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
|
||||
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
|
||||
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
|
||||
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
|
||||
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
|
||||
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
|
||||
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
|
||||
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
|
||||
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
|
||||
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
|
||||
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
|
||||
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
|
||||
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
|
||||
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
|
||||
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
|
||||
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
|
||||
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
|
||||
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
|
||||
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
|
||||
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
|
||||
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
|
||||
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
|
||||
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
|
||||
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
|
||||
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
|
||||
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
|
||||
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
|
||||
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
|
||||
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
|
||||
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
|
||||
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
|
||||
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
|
||||
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
|
||||
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
|
||||
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
|
||||
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
|
||||
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
|
||||
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
|
||||
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
|
||||
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
|
||||
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
|
||||
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
|
||||
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
|
||||
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
|
||||
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
|
||||
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
|
||||
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
|
||||
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
|
||||
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
|
||||
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
|
||||
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
|
||||
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
|
||||
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
|
||||
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
|
||||
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
|
||||
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
|
||||
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
|
||||
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
|
||||
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
|
||||
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
|
||||
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
|
||||
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
|
||||
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
|
||||
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
|
||||
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
|
||||
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
|
||||
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
|
||||
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
|
||||
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
|
||||
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
|
||||
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
|
||||
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
|
||||
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
|
||||
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
|
||||
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
|
||||
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
|
||||
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
|
||||
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
|
||||
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
|
||||
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
|
||||
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
|
||||
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
|
||||
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
|
||||
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
|
||||
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
|
||||
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
|
||||
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
|
||||
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
|
||||
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
|
||||
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
|
||||
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
|
||||
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
|
||||
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
||||
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
|
||||
};
|
||||
|
||||
// bcrypt IV: "OrpheanBeholderScryDoubt"
|
||||
static private final int bf_crypt_ciphertext[] = {
|
||||
0x4f727068, 0x65616e42, 0x65686f6c,
|
||||
0x64657253, 0x63727944, 0x6f756274
|
||||
};
|
||||
|
||||
// Table for Base64 encoding
|
||||
static private final char base64_code[] = {
|
||||
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
|
||||
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9'
|
||||
};
|
||||
|
||||
// Table for Base64 decoding
|
||||
static private final byte index_64[] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
|
||||
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
|
||||
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
||||
-1, -1, -1, -1, -1, -1, 28, 29, 30,
|
||||
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
||||
51, 52, 53, -1, -1, -1, -1, -1
|
||||
};
|
||||
|
||||
// Expanded Blowfish key
|
||||
private int P[];
|
||||
private int S[];
|
||||
|
||||
/**
|
||||
* Encode a byte array using bcrypt's slightly-modified base64
|
||||
* encoding scheme. Note that this is *not* compatible with
|
||||
* the standard MIME-base64 encoding.
|
||||
*
|
||||
* @param d the byte array to encode
|
||||
* @param len the number of bytes to encode
|
||||
* @return base64-encoded string
|
||||
* @exception IllegalArgumentException if the length is invalid
|
||||
*/
|
||||
private static String encode_base64(byte d[], int len)
|
||||
throws IllegalArgumentException {
|
||||
int off = 0;
|
||||
StringBuilder rs = new StringBuilder();
|
||||
int c1, c2;
|
||||
|
||||
if (len <= 0 || len > d.length) {
|
||||
throw new IllegalArgumentException ("Invalid len");
|
||||
}
|
||||
|
||||
while (off < len) {
|
||||
c1 = d[off++] & 0xff;
|
||||
rs.append(base64_code[c1 >> 2 & 0x3f]);
|
||||
c1 = (c1 & 0x03) << 4;
|
||||
if (off >= len) {
|
||||
rs.append(base64_code[c1 & 0x3f]);
|
||||
break;
|
||||
}
|
||||
c2 = d[off++] & 0xff;
|
||||
c1 |= c2 >> 4 & 0x0f;
|
||||
rs.append(base64_code[c1 & 0x3f]);
|
||||
c1 = (c2 & 0x0f) << 2;
|
||||
if (off >= len) {
|
||||
rs.append(base64_code[c1 & 0x3f]);
|
||||
break;
|
||||
}
|
||||
c2 = d[off++] & 0xff;
|
||||
c1 |= c2 >> 6 & 0x03;
|
||||
rs.append(base64_code[c1 & 0x3f]);
|
||||
rs.append(base64_code[c2 & 0x3f]);
|
||||
}
|
||||
return rs.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the 3 bits base64-encoded by the specified character, range-checking againt conversion table
|
||||
*
|
||||
* @param x the base64-encoded value
|
||||
* @return the decoded value of x
|
||||
*/
|
||||
private static byte char64(char x) {
|
||||
if (x < 0 || x > index_64.length) {
|
||||
return -1;
|
||||
}
|
||||
return index_64[x];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a string encoded using bcrypt's base64 scheme to a byte array. Note that this is *not* compatible with
|
||||
* the standard MIME-base64 encoding.
|
||||
*
|
||||
* @param s the string to decode
|
||||
* @param maxolen the maximum number of bytes to decode
|
||||
* @return an array containing the decoded bytes
|
||||
* @throws IllegalArgumentException if maxolen is invalid
|
||||
*/
|
||||
private static byte[] decode_base64(String s, int maxolen)
|
||||
throws IllegalArgumentException {
|
||||
StringBuilder rs = new StringBuilder();
|
||||
int off = 0, slen = s.length(), olen = 0;
|
||||
byte ret[];
|
||||
byte c1, c2, c3, c4, o;
|
||||
|
||||
if (maxolen <= 0) {
|
||||
throw new IllegalArgumentException ("Invalid maxolen");
|
||||
}
|
||||
|
||||
while (off < slen - 1 && olen < maxolen) {
|
||||
c1 = char64(s.charAt(off++));
|
||||
c2 = char64(s.charAt(off++));
|
||||
if (c1 == -1 || c2 == -1) {
|
||||
break;
|
||||
}
|
||||
o = (byte)(c1 << 2);
|
||||
o |= (c2 & 0x30) >> 4;
|
||||
rs.append((char)o);
|
||||
if (++olen >= maxolen || off >= slen) {
|
||||
break;
|
||||
}
|
||||
c3 = char64(s.charAt(off++));
|
||||
if (c3 == -1) {
|
||||
break;
|
||||
}
|
||||
o = (byte)((c2 & 0x0f) << 4);
|
||||
o |= (c3 & 0x3c) >> 2;
|
||||
rs.append((char)o);
|
||||
if (++olen >= maxolen || off >= slen) {
|
||||
break;
|
||||
}
|
||||
c4 = char64(s.charAt(off++));
|
||||
o = (byte)((c3 & 0x03) << 6);
|
||||
o |= c4;
|
||||
rs.append((char)o);
|
||||
++olen;
|
||||
}
|
||||
|
||||
ret = new byte[olen];
|
||||
for (off = 0; off < olen; off++) {
|
||||
ret[off] = (byte)rs.charAt(off);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blowfish encipher a single 64-bit block encoded as two 32-bit halves
|
||||
*
|
||||
* @param lr an array containing the two 32-bit half blocks
|
||||
* @param off the position in the array of the blocks
|
||||
*/
|
||||
private
|
||||
void encipher(int lr[], int off) {
|
||||
int i, n, l = lr[off], r = lr[off + 1];
|
||||
|
||||
l ^= P[0];
|
||||
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
|
||||
// Feistel substitution on left word
|
||||
n = S[l >> 24 & 0xff];
|
||||
n += S[0x100 | l >> 16 & 0xff];
|
||||
n ^= S[0x200 | l >> 8 & 0xff];
|
||||
n += S[0x300 | l & 0xff];
|
||||
r ^= n ^ P[++i];
|
||||
|
||||
// Feistel substitution on right word
|
||||
n = S[r >> 24 & 0xff];
|
||||
n += S[0x100 | r >> 16 & 0xff];
|
||||
n ^= S[0x200 | r >> 8 & 0xff];
|
||||
n += S[0x300 | r & 0xff];
|
||||
l ^= n ^ P[++i];
|
||||
}
|
||||
lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
|
||||
lr[off + 1] = l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycically extract a word of key material
|
||||
*
|
||||
* @param data the string to extract the data from
|
||||
* @param offp a "pointer" (as a one-entry array) to the current offset into data
|
||||
* @return the next word of material from data
|
||||
*/
|
||||
private static int streamtoword(byte data[], int offp[]) {
|
||||
int i;
|
||||
int word = 0;
|
||||
int off = offp[0];
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
word = word << 8 | data[off] & 0xff;
|
||||
off = (off + 1) % data.length;
|
||||
}
|
||||
|
||||
offp[0] = off;
|
||||
return word;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Blowfish key schedule
|
||||
*/
|
||||
private void init_key() {
|
||||
P = Arrays.copyOf(P_orig, P_orig.length);
|
||||
S = Arrays.copyOf(S_orig, S_orig.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key the Blowfish cipher
|
||||
* @param key an array containing the key
|
||||
*/
|
||||
private void key(byte key[]) {
|
||||
int i;
|
||||
int koffp[] = { 0 };
|
||||
int lr[] = { 0, 0 };
|
||||
int plen = P.length, slen = S.length;
|
||||
|
||||
for (i = 0; i < plen; i++) {
|
||||
P[i] = P[i] ^ streamtoword(key, koffp);
|
||||
}
|
||||
|
||||
for (i = 0; i < plen; i += 2) {
|
||||
encipher(lr, 0);
|
||||
P[i] = lr[0];
|
||||
P[i + 1] = lr[1];
|
||||
}
|
||||
|
||||
for (i = 0; i < slen; i += 2) {
|
||||
encipher(lr, 0);
|
||||
S[i] = lr[0];
|
||||
S[i + 1] = lr[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the "enhanced key schedule" step described by Provos and Mazieres in "A Future-Adaptable Password Scheme"
|
||||
* http://www.openbsd.org/papers/bcrypt-paper.ps
|
||||
*
|
||||
* @param data salt information
|
||||
* @param key password information
|
||||
*/
|
||||
private void ekskey(byte data[], byte key[]) {
|
||||
int i;
|
||||
int koffp[] = { 0 }, doffp[] = { 0 };
|
||||
int lr[] = { 0, 0 };
|
||||
int plen = P.length, slen = S.length;
|
||||
|
||||
for (i = 0; i < plen; i++) {
|
||||
P[i] = P[i] ^ streamtoword(key, koffp);
|
||||
}
|
||||
|
||||
for (i = 0; i < plen; i += 2) {
|
||||
lr[0] ^= streamtoword(data, doffp);
|
||||
lr[1] ^= streamtoword(data, doffp);
|
||||
encipher(lr, 0);
|
||||
P[i] = lr[0];
|
||||
P[i + 1] = lr[1];
|
||||
}
|
||||
|
||||
for (i = 0; i < slen; i += 2) {
|
||||
lr[0] ^= streamtoword(data, doffp);
|
||||
lr[1] ^= streamtoword(data, doffp);
|
||||
encipher(lr, 0);
|
||||
S[i] = lr[0];
|
||||
S[i + 1] = lr[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the central password hashing step in the bcrypt scheme
|
||||
*
|
||||
* @param password the password to hash
|
||||
* @param salt the binary salt to hash with the password
|
||||
* @param log_rounds the binary logarithm of the number of rounds of hashing to apply
|
||||
* @return an array containing the binary hashed password
|
||||
*/
|
||||
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, int cdata[]) {
|
||||
int rounds, i, j;
|
||||
int clen = cdata.length;
|
||||
byte ret[];
|
||||
|
||||
if (log_rounds < 4 || log_rounds > 30) {
|
||||
throw new IllegalArgumentException ("Bad number of rounds");
|
||||
}
|
||||
rounds = 1 << log_rounds;
|
||||
if (salt.length != BCRYPT_SALT_LEN) {
|
||||
throw new IllegalArgumentException ("Bad salt length");
|
||||
}
|
||||
|
||||
init_key();
|
||||
ekskey(salt, password);
|
||||
for (i = 0; i != rounds; i++) {
|
||||
key(password);
|
||||
key(salt);
|
||||
}
|
||||
|
||||
for (i = 0; i < 64; i++) {
|
||||
for (j = 0; j < clen >> 1; j++) {
|
||||
encipher(cdata, j << 1);
|
||||
}
|
||||
}
|
||||
|
||||
ret = new byte[clen * 4];
|
||||
for (i = 0, j = 0; i < clen; i++) {
|
||||
ret[j++] = (byte)(cdata[i] >> 24 & 0xff);
|
||||
ret[j++] = (byte)(cdata[i] >> 16 & 0xff);
|
||||
ret[j++] = (byte)(cdata[i] >> 8 & 0xff);
|
||||
ret[j++] = (byte)(cdata[i] & 0xff);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using the OpenBSD bcrypt scheme
|
||||
*
|
||||
* @param password the password to hash
|
||||
* @return the hashed password
|
||||
*/
|
||||
public static String hashpw(String password) {
|
||||
return hashpw(password, BCrypt.gensalt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using the OpenBSD bcrypt scheme
|
||||
*
|
||||
* @param password the password to hash
|
||||
* @param salt the salt to hash with (perhaps generated using BCrypt.gensalt)
|
||||
* @return the hashed password
|
||||
*/
|
||||
public static String hashpw(String password, String salt) {
|
||||
BCrypt B;
|
||||
String real_salt;
|
||||
byte passwordb[], saltb[], hashed[];
|
||||
char minor = (char)0;
|
||||
int rounds, off = 0;
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
||||
if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
|
||||
throw new IllegalArgumentException ("Invalid salt version");
|
||||
}
|
||||
if (salt.charAt(2) == '$') {
|
||||
off = 3;
|
||||
} else {
|
||||
minor = salt.charAt(2);
|
||||
if (minor != 'a' || salt.charAt(3) != '$') {
|
||||
throw new IllegalArgumentException ("Invalid salt revision");
|
||||
}
|
||||
off = 4;
|
||||
}
|
||||
|
||||
// Extract number of rounds
|
||||
if (salt.charAt(off + 2) > '$') {
|
||||
throw new IllegalArgumentException ("Missing salt rounds");
|
||||
}
|
||||
rounds = Integer.parseInt(salt.substring(off, off + 2));
|
||||
|
||||
real_salt = salt.substring(off + 3, off + 25);
|
||||
try {
|
||||
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new AssertionError("UTF-8 is not supported");
|
||||
}
|
||||
|
||||
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
|
||||
|
||||
B = new BCrypt();
|
||||
hashed = B.crypt_raw(passwordb, saltb, rounds, Arrays.copyOf(bf_crypt_ciphertext, bf_crypt_ciphertext.length));
|
||||
|
||||
rs.append("$2");
|
||||
if (minor >= 'a') {
|
||||
rs.append(minor);
|
||||
}
|
||||
rs.append("$");
|
||||
if (rounds < 10) {
|
||||
rs.append("0");
|
||||
}
|
||||
if (rounds > 30) {
|
||||
throw new IllegalArgumentException("rounds exceeds maximum (30)");
|
||||
}
|
||||
|
||||
rs.append(rounds);
|
||||
rs.append("$");
|
||||
rs.append(encode_base64(saltb, saltb.length));
|
||||
rs.append(encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1));
|
||||
return rs.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a salt for use with the BCrypt.hashpw() method
|
||||
* @param log_rounds the log2 of the number of rounds of
|
||||
* hashing to apply - the work factor therefore increases as
|
||||
* 2**log_rounds.
|
||||
* @param random an instance of SecureRandom to use
|
||||
* @return an encoded salt value
|
||||
*/
|
||||
public static String gensalt(int log_rounds, SecureRandom random) {
|
||||
StringBuilder rs = new StringBuilder();
|
||||
byte rnd[] = new byte[BCRYPT_SALT_LEN];
|
||||
|
||||
random.nextBytes(rnd);
|
||||
|
||||
rs.append("$2a$");
|
||||
if (log_rounds < 10) {
|
||||
rs.append("0");
|
||||
}
|
||||
rs.append(String.valueOf(log_rounds));
|
||||
rs.append("$");
|
||||
rs.append(encode_base64(rnd, rnd.length));
|
||||
return rs.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a salt for use with the BCrypt.hashpw() method
|
||||
* @param log_rounds the log2 of the number of rounds of
|
||||
* hashing to apply - the work factor therefore increases as
|
||||
* 2**log_rounds.
|
||||
* @return an encoded salt value
|
||||
*/
|
||||
public static String gensalt(int log_rounds) {
|
||||
return gensalt(log_rounds, new SecureRandom());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a salt for use with the BCrypt.hashpw() method,
|
||||
* selecting a reasonable default for the number of hashing
|
||||
* rounds to apply
|
||||
* @return an encoded salt value
|
||||
*/
|
||||
public static String gensalt() {
|
||||
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a plaintext password matches a previously hashed
|
||||
* one
|
||||
* @param plaintext the plaintext password to verify
|
||||
* @param hashed the previously-hashed password
|
||||
* @return true if the passwords match, false otherwise
|
||||
*/
|
||||
public static boolean checkpw(String plaintext, String hashed) {
|
||||
byte hashed_bytes[];
|
||||
byte try_bytes[];
|
||||
try {
|
||||
String try_pw = hashpw(plaintext, hashed);
|
||||
hashed_bytes = hashed.getBytes("UTF-8");
|
||||
try_bytes = try_pw.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
return false;
|
||||
}
|
||||
if (hashed_bytes.length != try_bytes.length)
|
||||
return false;
|
||||
byte ret = 0;
|
||||
for (int i = 0; i < try_bytes.length; i++)
|
||||
ret |= hashed_bytes[i] ^ try_bytes[i];
|
||||
|
||||
return ret == 0;
|
||||
}
|
||||
}
|
|
@ -1,485 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.PBEParametersGenerator;
|
||||
import org.bouncycastle.crypto.digests.MD5Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.lwjgl.util.xxhash.XXH32State;
|
||||
import org.lwjgl.util.xxhash.XXHash;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* http://en.wikipedia.org/wiki/NSA_Suite_B http://www.nsa.gov/ia/programs/suiteb_cryptography/
|
||||
* <p/>
|
||||
* NSA Suite B
|
||||
* <p/>
|
||||
* TOP-SECRET LEVEL AES256/GCM ECC with 384-bit prime curve (FIPS PUB 186-3), and SHA-384
|
||||
* <p/>
|
||||
* SECRET LEVEL AES 128 ECDH and ECDSA using the 256-bit prime (FIPS PUB 186-3), and SHA-256. RSA with 2048 can be used for DH key
|
||||
* negotiation
|
||||
* <p/>
|
||||
* WARNING! Note that this call is INCOMPATIBLE with GWT, so we have EXCLUDED IT from gwt, and created a CryptoGwt class in the web-client
|
||||
* project which only has the necessary crypto utility methods that are 1) Necessary 2) Compatible with GWT
|
||||
* <p/>
|
||||
* <p/>
|
||||
* To determine if we have hardware accelerated AES java -XX:+PrintFlagsFinal -version | grep UseAES
|
||||
*
|
||||
* Per NIST SP800-38D,
|
||||
* The total number of invocations of the authenticated encryption function shall not exceed 232, including all IV lengths and all instances of the authenticated encryption function with the given key.
|
||||
*
|
||||
*/
|
||||
public final
|
||||
class Crypto {
|
||||
|
||||
private
|
||||
Crypto() {
|
||||
}
|
||||
|
||||
// CUSTOM_HEADER USE
|
||||
// check to see if our extra data is OURS. if so, process it
|
||||
// cafeʞ, as UN signed bytes is: [254, 202, 202, 158], or as hex: FECA CA9E
|
||||
// cafeʞ, as signed bytes is: [-2, -54, -54, -98]
|
||||
private static final byte[] CUSTOM_HEADER = new byte[] {(byte) -2, (byte) -54, (byte) -54, (byte) -98};
|
||||
|
||||
public static
|
||||
void addProvider() {
|
||||
// make sure we only add it once (in case it's added elsewhere...)
|
||||
Provider provider = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
|
||||
if (provider == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if cryptography restrictions apply.
|
||||
* Restrictions apply if the value of {@link Cipher#getMaxAllowedKeyLength(String)} returns a value smaller than {@link Integer#MAX_VALUE} if there are any restrictions according to the JavaDoc of the method.
|
||||
* This method is used with the transform <code>"AES/CBC/PKCS5Padding"</code> as this is an often used algorithm that is <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#impl">an implementation requirement for Java SE</a>.
|
||||
*
|
||||
* @return <code>true</code> if restrictions apply, <code>false</code> otherwise
|
||||
*/
|
||||
public static boolean restrictedCryptography() {
|
||||
try {
|
||||
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding") < Integer.MAX_VALUE;
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("The transform \"AES/CBC/PKCS5Padding\" is not available (the availability of this algorithm is mandatory for Java SE implementations)", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
byte[] hashFileMD5(File file) {
|
||||
MD5Digest digest = new MD5Digest();
|
||||
return hashFile(file, digest, null);
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] hashFileSHA1(File file) {
|
||||
SHA1Digest digest = new SHA1Digest();
|
||||
return hashFile(file, digest, null);
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] hashFileSHA256(File file) {
|
||||
SHA256Digest digest = new SHA256Digest();
|
||||
return hashFile(file, digest, null);
|
||||
}
|
||||
|
||||
public static
|
||||
byte[] hashFileSHA512(File file) {
|
||||
SHA512Digest digest = new SHA512Digest();
|
||||
return hashFile(file, digest, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash of the file or NULL if file is invalid
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*/
|
||||
public static
|
||||
byte[] hashFile(File file, Digest digest, Logger logger) {
|
||||
return hashFile(file, digest, 0L, file.length(), logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash of the file or NULL if file is invalid
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*/
|
||||
public static
|
||||
byte[] hashFile(File file, Digest digest, long startPosition, long endPosition, Logger logger) {
|
||||
if (file.isFile() && file.canRead()) {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = new FileInputStream(file);
|
||||
long skip = inputStream.skip(startPosition);
|
||||
if (skip != startPosition) {
|
||||
throw new RuntimeException("Unable to skip " + startPosition + " bytes. Only skippped " + skip + " instead");
|
||||
}
|
||||
|
||||
long size = file.length() - startPosition;
|
||||
long lengthFromEnd = size - endPosition;
|
||||
|
||||
if (lengthFromEnd > 0 && lengthFromEnd < size) {
|
||||
size -= lengthFromEnd;
|
||||
}
|
||||
|
||||
int bufferSize = 4096;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
int readBytes;
|
||||
digest.reset();
|
||||
|
||||
while (size > 0) {
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int maxToRead = (int) Math.min(bufferSize, size);
|
||||
readBytes = inputStream.read(buffer, 0, maxToRead);
|
||||
size -= readBytes;
|
||||
|
||||
if (readBytes == 0) {
|
||||
//wtf. finally still gets called.
|
||||
return null;
|
||||
}
|
||||
|
||||
digest.update(buffer, 0, readBytes);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Error hashing file: {}", file.getAbsolutePath(), e);
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] digestBytes = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.doFinal(digestBytes, 0);
|
||||
return digestBytes;
|
||||
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the xxhash of the file as or 0 if file is invalid
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*/
|
||||
public static
|
||||
int xxHashFile(File file, long lengthFromEnd, Logger logger) {
|
||||
if (file.isFile() && file.canRead()) {
|
||||
InputStream inputStream = null;
|
||||
|
||||
// used to initialize the hash value, use whatever value you want, but always the same
|
||||
int seed = 0x9747b28c; // must match number in C (in Auth::xxHash32())
|
||||
|
||||
|
||||
XXH32State state = XXHash.XXH32_createState();
|
||||
XXHash.XXH32_reset(state, seed);
|
||||
|
||||
try {
|
||||
inputStream = new FileInputStream(file);
|
||||
long size = file.length();
|
||||
|
||||
if (lengthFromEnd > 0 && lengthFromEnd < size) {
|
||||
size -= lengthFromEnd;
|
||||
}
|
||||
|
||||
int bufferSize = 4096;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
ByteBuffer bbuffer = ByteBuffer.wrap(buffer);
|
||||
|
||||
|
||||
int readBytes;
|
||||
|
||||
while (size > 0) {
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int maxToRead = (int) Math.min(bufferSize, size);
|
||||
readBytes = inputStream.read(buffer, 0, maxToRead);
|
||||
size -= readBytes;
|
||||
|
||||
if (readBytes == 0) {
|
||||
//wtf. finally still gets called.
|
||||
return 0;
|
||||
}
|
||||
|
||||
bbuffer.limit(readBytes);
|
||||
XXHash.XXH32_update(state, bbuffer);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Error hashing file: {}", file.getAbsolutePath(), e);
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return XXHash.XXH32_digest(state);
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
int toInt(final byte[] bytes) {
|
||||
int number = 0;
|
||||
|
||||
switch (bytes.length) {
|
||||
default:
|
||||
case 4:
|
||||
number |= (bytes[3] & 0xFF) << 24;
|
||||
case 3:
|
||||
number |= (bytes[2] & 0xFF) << 16;
|
||||
case 2:
|
||||
number |= (bytes[1] & 0xFF) << 8;
|
||||
case 1:
|
||||
number |= (bytes[0] & 0xFF) << 0;
|
||||
}
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifically, to return the hash of the ALL files/directories inside the jar, minus the action specified (LGPL) files.
|
||||
*/
|
||||
public static
|
||||
byte[] hashJarContentsExcludeAction(File jarDestFilename, Digest digest, int action) throws IOException {
|
||||
JarFile jarDestFile = new JarFile(jarDestFilename);
|
||||
|
||||
try {
|
||||
Enumeration<JarEntry> jarElements = jarDestFile.entries();
|
||||
|
||||
boolean okToHash;
|
||||
boolean hasAction;
|
||||
byte[] buffer = new byte[2048];
|
||||
int read;
|
||||
digest.reset();
|
||||
|
||||
while (jarElements.hasMoreElements()) {
|
||||
JarEntry jarEntry = jarElements.nextElement();
|
||||
String name = jarEntry.getName();
|
||||
okToHash = !jarEntry.isDirectory();
|
||||
|
||||
if (!okToHash) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// data with NO extra data will NOT BE HASHED
|
||||
// data that matches our action bitmask WILL NOT BE HASHED
|
||||
|
||||
okToHash = false;
|
||||
hasAction = false;
|
||||
|
||||
byte[] extraData = jarEntry.getExtra();
|
||||
if (extraData == null || extraData.length == 0) {
|
||||
okToHash = false;
|
||||
}
|
||||
else if (extraData.length >= 4) {
|
||||
for (int i = 0; i < CUSTOM_HEADER.length; i++) {
|
||||
if (extraData[i] != CUSTOM_HEADER[i]) {
|
||||
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
|
||||
}
|
||||
}
|
||||
|
||||
// this means we matched our header
|
||||
if (extraData[4] > 0) {
|
||||
hasAction = true;
|
||||
|
||||
// we have an ACTION describing how it was compressed, etc
|
||||
int fileAction = toInt(new byte[] {extraData[5], extraData[6], extraData[7], extraData[8]});
|
||||
|
||||
if ((fileAction & action) != action) {
|
||||
okToHash = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
okToHash = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
|
||||
}
|
||||
|
||||
// skips hashing lgpl files. (technically, whatever our action bitmask is...)
|
||||
// we want to hash everything BY DEFAULT. we ALSO want to hash the NAME, LOAD ACTION TYPE, and the contents
|
||||
if (okToHash) {
|
||||
// System.err.println("HASHING: " + name);
|
||||
// hash the file name
|
||||
byte[] bytes = name.getBytes(StandardCharsets.US_ASCII);
|
||||
digest.update(bytes, 0, bytes.length);
|
||||
|
||||
if (hasAction) {
|
||||
// hash the action - since we don't want to permit anyone to change this after we sign the file
|
||||
digest.update(extraData, 5, 4);
|
||||
}
|
||||
|
||||
// hash the contents
|
||||
InputStream inputStream = jarDestFile.getInputStream(jarEntry);
|
||||
while ((read = inputStream.read(buffer)) > 0) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
inputStream.close();
|
||||
}
|
||||
//else {
|
||||
// System.err.println("Skipping: " + name);
|
||||
//}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
|
||||
} finally {
|
||||
jarDestFile.close();
|
||||
}
|
||||
|
||||
byte[] digestBytes = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.doFinal(digestBytes, 0);
|
||||
return digestBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash an input stream, based on the specified digest
|
||||
*/
|
||||
public static
|
||||
byte[] hashStream(Digest digest, InputStream inputStream) throws IOException {
|
||||
|
||||
byte[] buffer = new byte[2048];
|
||||
int read;
|
||||
digest.reset();
|
||||
|
||||
|
||||
while ((read = inputStream.read(buffer)) > 0) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
inputStream.close();
|
||||
|
||||
byte[] digestBytes = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.doFinal(digestBytes, 0);
|
||||
return digestBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure way to generate an AES key based on a password. Will '*' out the passed-in password
|
||||
*
|
||||
* @param password
|
||||
* will be filled with '*'
|
||||
* @param salt
|
||||
* should be a RANDOM number, at least 256bits (32 bytes) in size.
|
||||
* @param iterationCount
|
||||
* should be a lot, like 10,000
|
||||
*
|
||||
* @return the secure key to use
|
||||
*/
|
||||
public static
|
||||
byte[] PBKDF2(char[] password, byte[] salt, int iterationCount) {
|
||||
// will also zero out the password.
|
||||
byte[] charToBytes = Crypto.charToBytesPassword_UTF16(password);
|
||||
|
||||
return PBKDF2(charToBytes, salt, iterationCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure way to generate an AES key based on a password.
|
||||
*
|
||||
* @param password
|
||||
* The password that you want to mix
|
||||
* @param salt
|
||||
* should be a RANDOM number, at least 256bits (32 bytes) in size.
|
||||
* @param iterationCount
|
||||
* should be a lot, like 10,000
|
||||
*
|
||||
* @return the secure key to use
|
||||
*/
|
||||
public static
|
||||
byte[] PBKDF2(byte[] password, byte[] salt, int iterationCount) {
|
||||
SHA256Digest digest = new SHA256Digest();
|
||||
PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(digest);
|
||||
pGen.init(password, salt, iterationCount);
|
||||
|
||||
KeyParameter key = (KeyParameter) pGen.generateDerivedMacParameters(digest.getDigestSize() * 8); // *8 for bit length.
|
||||
|
||||
// zero out the password.
|
||||
Arrays.fill(password, (byte) 0);
|
||||
|
||||
return key.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* this saves the char array in UTF-16 format of bytes and BLANKS out the password char array.
|
||||
*/
|
||||
public static
|
||||
byte[] charToBytesPassword_UTF16(char[] password) {
|
||||
// note: this saves the char array in UTF-16 format of bytes.
|
||||
byte[] passwordBytes = new byte[password.length * 2];
|
||||
for (int i = 0; i < password.length; i++) {
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
passwordBytes[2 * i] = (byte) (((int) password[i] & 0xFF00) >> 8);
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
passwordBytes[2 * i + 1] = (byte) ((int) password[i] & 0x00FF);
|
||||
}
|
||||
|
||||
// asterisk out the password
|
||||
Arrays.fill(password, '*');
|
||||
|
||||
return passwordBytes;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,625 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* AES crypto functions
|
||||
*/
|
||||
@SuppressWarnings({"Duplicates"})
|
||||
public final
|
||||
class CryptoAES {
|
||||
private static final int ivSize = 16;
|
||||
|
||||
/**
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] encryptWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
byte[] encryptAES = encrypt(aesEngine, aesKey, aesIV, data, logger);
|
||||
|
||||
int length = encryptAES.length;
|
||||
|
||||
byte[] out = new byte[length + ivSize];
|
||||
System.arraycopy(aesIV, 0, out, 0, ivSize);
|
||||
System.arraycopy(encryptAES, 0, out, ivSize, length);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
byte[] encryptWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
|
||||
byte[] encryptAES = encrypt(aesEngine, aesKey, aesIV, data, logger);
|
||||
|
||||
int length = encryptAES.length;
|
||||
|
||||
byte[] out = new byte[length + ivSize];
|
||||
System.arraycopy(aesIV, 0, out, 0, ivSize);
|
||||
System.arraycopy(encryptAES, 0, out, ivSize, length);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
public static
|
||||
boolean encryptStreamWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
|
||||
try {
|
||||
out.write(aesIV);
|
||||
} catch (IOException e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return encryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
boolean encryptStreamWithIV(BufferedBlockCipher aesEngine,
|
||||
byte[] aesKey,
|
||||
byte[] aesIV,
|
||||
InputStream in,
|
||||
OutputStream out,
|
||||
Logger logger) {
|
||||
|
||||
try {
|
||||
out.write(aesIV);
|
||||
} catch (IOException e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return encryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] encrypt(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
int length = data.length;
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
return encrypt(aesEngine, aesIVAndKey, data, length, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return length of encrypted data, -1 if there was an error.
|
||||
*/
|
||||
public static
|
||||
byte[] encrypt(GCMBlockCipher aesEngine, CipherParameters aesIVAndKey, byte[] data, int length, Logger logger) {
|
||||
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
byte[] outArray = new byte[minSize];
|
||||
|
||||
int actualLength = aesEngine.processBytes(data, 0, length, outArray, 0);
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(outArray, actualLength);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
if (outArray.length == actualLength) {
|
||||
return outArray;
|
||||
}
|
||||
else {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(outArray, 0, result, 0, result.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES encrypts data with a specified key.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
byte[] encrypt(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
int length = data.length;
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
byte[] outBuf = new byte[minSize];
|
||||
|
||||
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
if (outBuf.length == actualLength) {
|
||||
return outBuf;
|
||||
}
|
||||
else {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES encrypt from one stream to another.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
boolean encryptStream(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] buf = new byte[ivSize];
|
||||
byte[] outbuf = new byte[512];
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
try {
|
||||
int bytesRead;
|
||||
int bytesProcessed;
|
||||
|
||||
while ((bytesRead = in.read(buf)) >= 0) {
|
||||
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
}
|
||||
|
||||
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* AES encrypt from one stream to another.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
public static
|
||||
boolean encryptStream(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
|
||||
byte[] buf = new byte[ivSize];
|
||||
byte[] outbuf = new byte[512];
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
try {
|
||||
int bytesRead;
|
||||
int bytesProcessed;
|
||||
|
||||
while ((bytesRead = in.read(buf)) >= 0) {
|
||||
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
}
|
||||
|
||||
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* AES decrypt (if the aes IV is included in the data). IV must be a nonce (unique value) !!
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decryptWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] data, Logger logger) {
|
||||
byte[] aesIV = new byte[ivSize];
|
||||
System.arraycopy(data, 0, aesIV, 0, ivSize);
|
||||
|
||||
byte[] in = new byte[data.length - ivSize];
|
||||
System.arraycopy(data, ivSize, in, 0, in.length);
|
||||
|
||||
return decrypt(aesEngine, aesKey, aesIV, in, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES decrypt (if the aes IV is included in the data). IV must be a nonce (unique value)
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
byte[] decryptWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] data, Logger logger) {
|
||||
byte[] aesIV = new byte[ivSize];
|
||||
System.arraycopy(data, 0, aesIV, 0, ivSize);
|
||||
|
||||
byte[] in = new byte[data.length - ivSize];
|
||||
System.arraycopy(data, ivSize, in, 0, in.length);
|
||||
|
||||
return decrypt(aesEngine, aesKey, aesIV, in, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES decrypt (if the aes IV is included in the data. IV must be a nonce (unique value)
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
public static
|
||||
boolean decryptStreamWithIV(GCMBlockCipher aesEngine, byte[] aesKey, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] aesIV = new byte[ivSize];
|
||||
try {
|
||||
in.read(aesIV, 0, ivSize);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return decryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES decrypt (if the aes IV is included in the data). IV must be a nonce (unique value)
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
boolean decryptStreamWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] aesIV = new byte[ivSize];
|
||||
try {
|
||||
in.read(aesIV, 0, ivSize);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return decryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decrypt(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
int length = data.length;
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
byte[] outBuf = new byte[minSize];
|
||||
|
||||
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.debug("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
if (outBuf.length == actualLength) {
|
||||
return outBuf;
|
||||
}
|
||||
else {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
byte[] decrypt(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||
|
||||
int length = data.length;
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
byte[] outBuf = new byte[minSize];
|
||||
|
||||
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
if (outBuf.length == actualLength) {
|
||||
return outBuf;
|
||||
}
|
||||
else {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AES decrypt from one stream to another.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
public static
|
||||
boolean decryptStream(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] buf = new byte[ivSize];
|
||||
byte[] outbuf = new byte[512];
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
try {
|
||||
int bytesRead;
|
||||
int bytesProcessed;
|
||||
|
||||
while ((bytesRead = in.read(buf)) >= 0) {
|
||||
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
}
|
||||
|
||||
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||
* <p>
|
||||
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||
* data.
|
||||
* <p>
|
||||
* AES decrypt from one stream to another.
|
||||
*
|
||||
* @param aesIV
|
||||
* must be a nonce (unique value) !!
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
@Deprecated
|
||||
public static
|
||||
boolean decryptStream(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||
byte[] buf = new byte[ivSize];
|
||||
byte[] outbuf = new byte[512];
|
||||
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
try {
|
||||
int bytesRead;
|
||||
int bytesProcessed;
|
||||
|
||||
while ((bytesRead = in.read(buf)) >= 0) {
|
||||
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
}
|
||||
|
||||
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||
|
||||
out.write(outbuf, 0, bytesProcessed);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private
|
||||
CryptoAES() {
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||
import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.generators.DSAParametersGenerator;
|
||||
import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.DSAParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.crypto.signers.DSASigner;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* this is here just for keeping track of how this is done. This should correct and working, but should NOT be used, and instead use ECC
|
||||
* crypto.
|
||||
*/
|
||||
@Deprecated
|
||||
public final
|
||||
class CryptoDSA {
|
||||
/**
|
||||
* Generates the DSA key (using RSA and SHA1)
|
||||
* <p/>
|
||||
* Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||
*/
|
||||
public static
|
||||
AsymmetricCipherKeyPair generateKeyPair(SecureRandom secureRandom, int keyLength) {
|
||||
DSAKeyPairGenerator keyGen = new DSAKeyPairGenerator();
|
||||
|
||||
DSAParametersGenerator dsaParametersGenerator = new DSAParametersGenerator();
|
||||
dsaParametersGenerator.init(keyLength, 20, secureRandom);
|
||||
DSAParameters generateParameters = dsaParametersGenerator.generateParameters();
|
||||
|
||||
DSAKeyGenerationParameters params = new DSAKeyGenerationParameters(secureRandom, generateParameters);
|
||||
keyGen.init(params);
|
||||
return keyGen.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will have the SHA1 hash calculated and used for the signature.
|
||||
* <p/>
|
||||
* Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||
* <p/>
|
||||
* The returned signature is the {r,s} signature array.
|
||||
*/
|
||||
public static
|
||||
BigInteger[] generateSignature(DSAPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] message) {
|
||||
ParametersWithRandom param = new ParametersWithRandom(privateKey, secureRandom);
|
||||
|
||||
DSASigner dsa = new DSASigner();
|
||||
|
||||
dsa.init(true, param);
|
||||
|
||||
|
||||
SHA1Digest sha1Digest = new SHA1Digest();
|
||||
byte[] checksum = new byte[sha1Digest.getDigestSize()];
|
||||
|
||||
sha1Digest.update(message, 0, message.length);
|
||||
sha1Digest.doFinal(checksum, 0);
|
||||
|
||||
return dsa.generateSignature(checksum);
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will have the SHA1 hash calculated and used for the signature.
|
||||
* <p/>
|
||||
* Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||
*
|
||||
* @param signature
|
||||
* is the {r,s} signature array.
|
||||
*
|
||||
* @return true if the signature is valid
|
||||
*/
|
||||
public static
|
||||
boolean verifySignature(DSAPublicKeyParameters publicKey, byte[] message, BigInteger[] signature) {
|
||||
SHA1Digest sha1Digest = new SHA1Digest();
|
||||
byte[] checksum = new byte[sha1Digest.getDigestSize()];
|
||||
|
||||
sha1Digest.update(message, 0, message.length);
|
||||
sha1Digest.doFinal(checksum, 0);
|
||||
|
||||
|
||||
DSASigner dsa = new DSASigner();
|
||||
|
||||
dsa.init(false, publicKey);
|
||||
|
||||
return dsa.verifySignature(checksum, signature[0], signature[1]);
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
CryptoDSA() {
|
||||
}
|
||||
}
|
|
@ -1,409 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.digests.SHA384Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||
import org.bouncycastle.crypto.engines.IESEngine;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
|
||||
import org.bouncycastle.crypto.macs.HMac;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.IESParameters;
|
||||
import org.bouncycastle.crypto.params.IESWithCipherParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.jcajce.provider.util.DigestFactory;
|
||||
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.math.ec.ECFieldElement;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* ECC crypto functions
|
||||
*/
|
||||
public final
|
||||
class CryptoECC {
|
||||
public static final String p521_curve = "secp521r1";
|
||||
public static final String curve25519 = "curve25519";
|
||||
public static final String default_curve = curve25519;
|
||||
|
||||
public static final int macSize = 512;
|
||||
// on NIST vs 25519 vs Brainpool, see:
|
||||
// - http://ogryb.blogspot.de/2014/11/why-i-dont-trust-nist-p-256.html
|
||||
// - http://credelius.com/credelius/?p=97
|
||||
// - http://safecurves.cr.yp.to/
|
||||
// we should be using 25519, because NIST and brainpool are "unsafe". Brainpool is "more random" than 25519, but is still not considered safe.
|
||||
|
||||
// more info about ECC from:
|
||||
// http://www.johannes-bauer.com/compsci/ecc/?menuid=4
|
||||
// http://stackoverflow.com/questions/7419183/problems-implementing-ecdh-on-android-using-bouncycastle
|
||||
// http://tools.ietf.org/html/draft-jivsov-openpgp-ecc-06#page-4
|
||||
// http://www.nsa.gov/ia/programs/suiteb_cryptography/
|
||||
// https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java
|
||||
// http://nelenkov.blogspot.com/2011/12/using-ecdh-on-android.html
|
||||
// http://www.secg.org/collateral/sec1_final.pdf
|
||||
|
||||
/**
|
||||
* Uses SHA512
|
||||
*/
|
||||
public static
|
||||
IESEngine createEngine() {
|
||||
return new IESEngine(new ECDHCBasicAgreement(), new KDF2BytesGenerator(new SHA384Digest()), new HMac(new SHA512Digest()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses SHA512
|
||||
*/
|
||||
public static
|
||||
IESEngine createEngine(PaddedBufferedBlockCipher aesEngine) {
|
||||
return new IESEngine(new ECDHCBasicAgreement(),
|
||||
new KDF2BytesGenerator(new SHA384Digest()),
|
||||
new HMac(new SHA512Digest()),
|
||||
aesEngine);
|
||||
}
|
||||
|
||||
/**
|
||||
* These parameters are shared between the two parties. These are a NONCE (use ONCE number!!)
|
||||
*/
|
||||
public static
|
||||
IESParameters generateSharedParameters(SecureRandom secureRandom) {
|
||||
|
||||
int macSize = CryptoECC.macSize; // must be the MAC size
|
||||
|
||||
// MUST be random EACH TIME encrypt/sign happens!
|
||||
byte[] derivation = new byte[macSize / 8];
|
||||
byte[] encoding = new byte[macSize / 8];
|
||||
|
||||
secureRandom.nextBytes(derivation);
|
||||
secureRandom.nextBytes(encoding);
|
||||
|
||||
return new IESParameters(derivation, encoding, macSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES-256 ONLY!
|
||||
*/
|
||||
public static
|
||||
IESWithCipherParameters generateSharedParametersWithCipher(SecureRandom secureRandom) {
|
||||
int macSize = CryptoECC.macSize; // must be the MAC size
|
||||
|
||||
byte[] derivation = new byte[macSize / 8]; // MUST be random EACH TIME encrypt/sign happens!
|
||||
byte[] encoding = new byte[macSize / 8];
|
||||
|
||||
secureRandom.nextBytes(derivation);
|
||||
secureRandom.nextBytes(encoding);
|
||||
|
||||
return new IESWithCipherParameters(derivation, encoding, macSize, 256);
|
||||
}
|
||||
|
||||
public static
|
||||
AsymmetricCipherKeyPair generateKeyPair(String eccCurveName, SecureRandom secureRandom) {
|
||||
ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(eccCurveName);
|
||||
|
||||
return generateKeyPair(eccSpec, secureRandom);
|
||||
}
|
||||
|
||||
public static
|
||||
AsymmetricCipherKeyPair generateKeyPair(ECParameterSpec eccSpec, SecureRandom secureRandom) {
|
||||
ECKeyGenerationParameters ecParams = new ECKeyGenerationParameters(new ECDomainParameters(eccSpec.getCurve(),
|
||||
eccSpec.getG(),
|
||||
eccSpec.getN()), secureRandom);
|
||||
|
||||
ECKeyPairGenerator ecKeyGen = new ECKeyPairGenerator();
|
||||
ecKeyGen.init(ecParams);
|
||||
|
||||
return ecKeyGen.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* ECC encrypts data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] encrypt(IESEngine eccEngine,
|
||||
CipherParameters private1,
|
||||
CipherParameters public2,
|
||||
IESParameters cipherParams,
|
||||
byte[] message,
|
||||
Logger logger) {
|
||||
|
||||
eccEngine.init(true, private1, public2, cipherParams);
|
||||
|
||||
//noinspection Duplicates
|
||||
try {
|
||||
return eccEngine.processBlock(message, 0, message.length);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform ECC cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ECC decrypt data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decrypt(IESEngine eccEngine,
|
||||
CipherParameters private2,
|
||||
CipherParameters public1,
|
||||
IESParameters cipherParams,
|
||||
byte[] encrypted,
|
||||
Logger logger) {
|
||||
|
||||
eccEngine.init(false, private2, public1, cipherParams);
|
||||
|
||||
//noinspection Duplicates
|
||||
try {
|
||||
return eccEngine.processBlock(encrypted, 0, encrypted.length);
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform ECC cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
public static
|
||||
boolean compare(ECPrivateKeyParameters privateA, ECPrivateKeyParameters privateB) {
|
||||
ECDomainParameters parametersA = privateA.getParameters();
|
||||
ECDomainParameters parametersB = privateB.getParameters();
|
||||
|
||||
// is it the same curve?
|
||||
boolean equals = parametersA.getCurve()
|
||||
.equals(parametersB.getCurve());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = parametersA.getG()
|
||||
.equals(parametersB.getG());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
equals = parametersA.getH()
|
||||
.equals(parametersB.getH());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = parametersA.getN()
|
||||
.equals(parametersB.getN());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = privateA.getD()
|
||||
.equals(privateB.getD());
|
||||
|
||||
return equals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if publicA and publicB are NOT NULL, and are both equal to eachother
|
||||
*/
|
||||
@SuppressWarnings({"RedundantIfStatement", "SpellCheckingInspection"})
|
||||
public static
|
||||
boolean compare(ECPublicKeyParameters publicA, ECPublicKeyParameters publicB) {
|
||||
if (publicA == null || publicB == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ECDomainParameters parametersA = publicA.getParameters();
|
||||
ECDomainParameters parametersB = publicB.getParameters();
|
||||
|
||||
// is it the same curve?
|
||||
boolean equals = parametersA.getCurve()
|
||||
.equals(parametersB.getCurve());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = parametersA.getG()
|
||||
.equals(parametersB.getG());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
equals = parametersA.getH()
|
||||
.equals(parametersB.getH());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
equals = parametersA.getN()
|
||||
.equals(parametersB.getN());
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ECPoint normalizeA = publicA.getQ()
|
||||
.normalize();
|
||||
ECPoint normalizeB = publicB.getQ()
|
||||
.normalize();
|
||||
|
||||
|
||||
ECFieldElement xCoordA = normalizeA.getXCoord();
|
||||
ECFieldElement xCoordB = normalizeB.getXCoord();
|
||||
|
||||
equals = xCoordA.equals(xCoordB);
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ECFieldElement yCoordA = normalizeA.getYCoord();
|
||||
ECFieldElement yCoordB = normalizeB.getYCoord();
|
||||
|
||||
equals = yCoordA.equals(yCoordB);
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
public static
|
||||
boolean compare(IESParameters cipherAParams, IESParameters cipherBParams) {
|
||||
if (!Arrays.equals(cipherAParams.getDerivationV(), cipherBParams.getDerivationV())) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(cipherAParams.getEncodingV(), cipherBParams.getEncodingV())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cipherAParams.getMacKeySize() != cipherBParams.getMacKeySize()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static
|
||||
boolean compare(IESWithCipherParameters cipherAParams, IESWithCipherParameters cipherBParams) {
|
||||
if (cipherAParams.getCipherKeySize() != cipherBParams.getCipherKeySize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only need to cast one side.
|
||||
return compare((IESParameters) cipherAParams, cipherBParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will have the (digestName) hash calculated and used for the signature.
|
||||
* <p/>
|
||||
* The returned signature is the {r,s} signature array.
|
||||
*/
|
||||
public static
|
||||
BigInteger[] generateSignature(String digestName, ECPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] bytes) {
|
||||
|
||||
Digest digest = DigestFactory.getDigest(digestName);
|
||||
|
||||
byte[] checksum = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.update(bytes, 0, bytes.length);
|
||||
digest.doFinal(checksum, 0);
|
||||
|
||||
return generateSignatureForHash(privateKey, secureRandom, checksum);
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will use the bytes AS THE HASHED VALUE to calculate the signature.
|
||||
* <p/>
|
||||
* The returned signature is the {r,s} signature array.
|
||||
*/
|
||||
public static
|
||||
BigInteger[] generateSignatureForHash(ECPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] hashBytes) {
|
||||
|
||||
ParametersWithRandom param = new ParametersWithRandom(privateKey, secureRandom);
|
||||
|
||||
ECDSASigner ecdsa = new ECDSASigner();
|
||||
ecdsa.init(true, param);
|
||||
|
||||
return ecdsa.generateSignature(hashBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The message will have the (digestName) hash calculated and used for the signature.
|
||||
*
|
||||
* @param signature
|
||||
* is the {r,s} signature array.
|
||||
*
|
||||
* @return true if the signature is valid
|
||||
*/
|
||||
public static
|
||||
boolean verifySignature(String digestName, ECPublicKeyParameters publicKey, byte[] message, BigInteger[] signature) {
|
||||
|
||||
Digest digest = DigestFactory.getDigest(digestName);
|
||||
|
||||
byte[] checksum = new byte[digest.getDigestSize()];
|
||||
|
||||
digest.update(message, 0, message.length);
|
||||
digest.doFinal(checksum, 0);
|
||||
|
||||
|
||||
return verifySignatureHash(publicKey, checksum, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* The provided hash will be used in the signature verification.
|
||||
*
|
||||
* @param signature
|
||||
* is the {r,s} signature array.
|
||||
*
|
||||
* @return true if the signature is valid
|
||||
*/
|
||||
public static
|
||||
boolean verifySignatureHash(ECPublicKeyParameters publicKey, byte[] hash, BigInteger[] signature) {
|
||||
|
||||
ECDSASigner ecdsa = new ECDSASigner();
|
||||
ecdsa.init(false, publicKey);
|
||||
|
||||
|
||||
return ecdsa.verifySignature(hash, signature[0], signature[1]);
|
||||
}
|
||||
|
||||
private
|
||||
CryptoECC() {
|
||||
}
|
||||
}
|
|
@ -1,834 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.bcpg.BCPGOutputStream;
|
||||
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
|
||||
|
||||
import dorkbox.util.IO;
|
||||
|
||||
/**
|
||||
* PGP crypto related methods
|
||||
*/
|
||||
public final
|
||||
class CryptoPGP {
|
||||
private static final BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider();
|
||||
private static final BcKeyFingerprintCalculator fingerprintCalculator = new BcKeyFingerprintCalculator();
|
||||
|
||||
|
||||
// https://github.com/weiliatgithub/bouncycastle-gpg-exampleC
|
||||
// https://gist.github.com/turingbirds/3df43f1920a98010667a
|
||||
// http://sloanseaman.com/wordpress/2012/05/13/revisited-pgp-encryptiondecryption-in-java/
|
||||
// http://bouncycastle-pgp-cookbook.blogspot.de/
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt"
|
||||
*
|
||||
* @param privateKeyInputStream
|
||||
* this is an armored key file, not a binary stream
|
||||
* @param userId
|
||||
* this is the userID to get out of the private key
|
||||
* @param password
|
||||
* this is the password to unlock the private key
|
||||
* @param messageAsUtf8Bytes
|
||||
* this is the message, in bytes, to sign
|
||||
*/
|
||||
public static
|
||||
byte[] signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, byte[] messageAsUtf8Bytes)
|
||||
throws PGPException {
|
||||
|
||||
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
|
||||
return sign(privateKeyInputStream,
|
||||
userId,
|
||||
password,
|
||||
new ByteArrayInputStream(messageAsUtf8Bytes),
|
||||
PGPSignature.BINARY_DOCUMENT,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt"
|
||||
*
|
||||
* @param privateKeyInputStream
|
||||
* this is an armored key file, not a binary stream
|
||||
* @param userId
|
||||
* this is the userID to get out of the private key
|
||||
* @param password
|
||||
* this is the password to unlock the private key
|
||||
* @param message
|
||||
* this is the message to sign
|
||||
*/
|
||||
public static
|
||||
byte[] signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, InputStream message)
|
||||
throws PGPException {
|
||||
|
||||
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
|
||||
return sign(privateKeyInputStream,
|
||||
userId,
|
||||
password,
|
||||
message,
|
||||
PGPSignature.BINARY_DOCUMENT,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt". This will save the signature of the passed-in
|
||||
* file to file name + .asc
|
||||
*
|
||||
* @param privateKeyInputStream
|
||||
* this is an armored key file, not a binary stream
|
||||
* @param userId
|
||||
* this is the userID to get out of the private key
|
||||
* @param password
|
||||
* this is the password to unlock the private key
|
||||
* @param file
|
||||
* this is the file to sign
|
||||
*/
|
||||
public static
|
||||
void signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, File file)
|
||||
throws PGPException {
|
||||
|
||||
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
|
||||
final byte[] sign = sign(privateKeyInputStream,
|
||||
userId,
|
||||
password,
|
||||
file,
|
||||
PGPSignature.BINARY_DOCUMENT,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
|
||||
FileOutputStream fileOutputStream1 = null;
|
||||
try {
|
||||
fileOutputStream1 = new FileOutputStream(new File(file.getAbsolutePath() + ".asc"));
|
||||
fileOutputStream1.write(sign);
|
||||
fileOutputStream1.flush();
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new PGPException("Unable to save signature to file " + file.getAbsolutePath() + ".asc", e);
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to save signature to file " + file.getAbsolutePath() + ".asc", e);
|
||||
} finally {
|
||||
IO.close(fileOutputStream1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, with a variety of options
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public static
|
||||
byte[] sign(InputStream privateKeyInputStream,
|
||||
String userId,
|
||||
char[] password,
|
||||
InputStream message,
|
||||
int signatureType,
|
||||
boolean compressSignature,
|
||||
boolean asciiArmoredOutput,
|
||||
boolean includeDataInSignature,
|
||||
boolean generateUserIdSubPacket,
|
||||
boolean generateOnePassVersion) throws PGPException {
|
||||
|
||||
List<PGPSecretKey> secretKeys = getSecretKeys(privateKeyInputStream, userId);
|
||||
PGPSignatureGenerator signature = createSignature(secretKeys, password, signatureType, generateUserIdSubPacket);
|
||||
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
OutputStream outputStream = byteArrayOutputStream;
|
||||
if (asciiArmoredOutput) {
|
||||
outputStream = new ArmoredOutputStream(byteArrayOutputStream);
|
||||
}
|
||||
|
||||
PGPCompressedDataGenerator compressedDataGenerator = null;
|
||||
BCPGOutputStream bcOutputStream;
|
||||
|
||||
if (compressSignature) {
|
||||
compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
|
||||
try {
|
||||
bcOutputStream = new BCPGOutputStream(compressedDataGenerator.open(outputStream));
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to open compression stream in the signature", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bcOutputStream = new BCPGOutputStream(outputStream);
|
||||
}
|
||||
|
||||
if (generateOnePassVersion) {
|
||||
try {
|
||||
signature.generateOnePassVersion(false)
|
||||
.encode(bcOutputStream);
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to generate OnePass signature header", e);
|
||||
}
|
||||
}
|
||||
|
||||
PGPLiteralDataGenerator literalDataGenerator = null;
|
||||
OutputStream literalDataOutput = null;
|
||||
|
||||
if (includeDataInSignature) {
|
||||
literalDataGenerator = new PGPLiteralDataGenerator();
|
||||
try {
|
||||
literalDataOutput = literalDataGenerator.open(bcOutputStream,
|
||||
PGPLiteralData.BINARY,
|
||||
"_CONSOLE",
|
||||
message.available(),
|
||||
new Date());
|
||||
} catch (IOException e1) {
|
||||
throw new PGPException("Unable to generate Literal Data signature header", e1);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
// update bytes in the streams
|
||||
if (literalDataOutput != null) {
|
||||
while ((read = message.read(buffer)) > 0) {
|
||||
literalDataOutput.write(buffer, 0, read);
|
||||
signature.update(buffer, 0, read);
|
||||
}
|
||||
literalDataOutput.flush();
|
||||
} else {
|
||||
|
||||
while ((read = message.read(buffer)) > 0) {
|
||||
signature.update(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
// close generators and update signature
|
||||
if (literalDataGenerator != null) {
|
||||
literalDataGenerator.close();
|
||||
}
|
||||
|
||||
signature.generate()
|
||||
.encode(bcOutputStream);
|
||||
|
||||
|
||||
if (compressedDataGenerator != null) {
|
||||
compressedDataGenerator.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
IO.close(bcOutputStream);
|
||||
IO.close(outputStream);
|
||||
IO.close(literalDataOutput);
|
||||
}
|
||||
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message using our private PGP key file, with a variety of options
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public static
|
||||
byte[] sign(InputStream privateKeyInputStream,
|
||||
String userId,
|
||||
char[] password,
|
||||
File fileMessage,
|
||||
int signatureType,
|
||||
boolean compressSignature,
|
||||
boolean asciiArmoredOutput,
|
||||
boolean includeDataInSignature,
|
||||
boolean generateUserIdSubPacket,
|
||||
boolean generateOnePassVersion) throws PGPException {
|
||||
|
||||
List<PGPSecretKey> secretKeys = getSecretKeys(privateKeyInputStream, userId);
|
||||
PGPSignatureGenerator signature = createSignature(secretKeys, password, signatureType, generateUserIdSubPacket);
|
||||
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
OutputStream outputStream = byteArrayOutputStream;
|
||||
if (asciiArmoredOutput) {
|
||||
outputStream = new ArmoredOutputStream(byteArrayOutputStream);
|
||||
}
|
||||
|
||||
PGPCompressedDataGenerator compressedDataGenerator = null;
|
||||
BCPGOutputStream bcOutputStream;
|
||||
|
||||
if (compressSignature) {
|
||||
compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
|
||||
try {
|
||||
bcOutputStream = new BCPGOutputStream(compressedDataGenerator.open(outputStream));
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to open compression stream in the signature", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bcOutputStream = new BCPGOutputStream(outputStream);
|
||||
}
|
||||
|
||||
if (generateOnePassVersion) {
|
||||
try {
|
||||
signature.generateOnePassVersion(false)
|
||||
.encode(bcOutputStream);
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("Unable to generate OnePass signature header", e);
|
||||
}
|
||||
}
|
||||
|
||||
PGPLiteralDataGenerator literalDataGenerator = null;
|
||||
OutputStream literalDataOutput = null;
|
||||
|
||||
if (includeDataInSignature) {
|
||||
literalDataGenerator = new PGPLiteralDataGenerator();
|
||||
try {
|
||||
literalDataOutput = literalDataGenerator.open(bcOutputStream,
|
||||
PGPLiteralData.BINARY,
|
||||
fileMessage);
|
||||
} catch (IOException e1) {
|
||||
throw new PGPException("Unable to generate Literal Data signature header", e1);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final FileInputStream fileInputStream = new FileInputStream(fileMessage);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
// update bytes in the streams
|
||||
if (literalDataOutput != null) {
|
||||
while ((read = fileInputStream.read(buffer)) > 0) {
|
||||
literalDataOutput.write(buffer, 0, read);
|
||||
signature.update(buffer, 0, read);
|
||||
}
|
||||
literalDataOutput.flush();
|
||||
} else {
|
||||
|
||||
while ((read = fileInputStream.read(buffer)) > 0) {
|
||||
signature.update(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
// close generators and update signature
|
||||
if (literalDataGenerator != null) {
|
||||
literalDataGenerator.close();
|
||||
}
|
||||
|
||||
signature.generate()
|
||||
.encode(bcOutputStream);
|
||||
|
||||
|
||||
if (compressedDataGenerator != null) {
|
||||
compressedDataGenerator.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
IO.close(bcOutputStream);
|
||||
IO.close(outputStream);
|
||||
IO.close(literalDataOutput);
|
||||
}
|
||||
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find private gpg key in InputStream, also closes the input stream
|
||||
*
|
||||
* @param inputStream
|
||||
* the inputStream that contains the private (secret) key
|
||||
* @param userId
|
||||
* the user id
|
||||
*
|
||||
* @return the PGP secret key
|
||||
*/
|
||||
public static
|
||||
List<PGPSecretKey> getSecretKeys(InputStream inputStream, String userId) throws PGPException {
|
||||
// iterate over every private key in the key ring
|
||||
PGPSecretKeyRingCollection secretKeyRings;
|
||||
try {
|
||||
secretKeyRings = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(inputStream), fingerprintCalculator);
|
||||
} catch (IOException e) {
|
||||
throw new PGPException("No private key found in stream!", e);
|
||||
} finally {
|
||||
IO.close(inputStream);
|
||||
}
|
||||
|
||||
// look for the key ring that is used to authenticate our reporting facilities
|
||||
Iterator<PGPSecretKeyRing> secretKeys = secretKeyRings.getKeyRings(userId);
|
||||
List<PGPSecretKey> pgpSecretKeys = new ArrayList<PGPSecretKey>();
|
||||
|
||||
// iterate over every private key in the ring
|
||||
while (secretKeys.hasNext()) {
|
||||
PGPSecretKeyRing secretKeyRing = secretKeys.next();
|
||||
PGPSecretKey tmpKey = secretKeyRing.getSecretKey();
|
||||
|
||||
if (tmpKey != null) {
|
||||
pgpSecretKeys.add(tmpKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pgpSecretKeys.isEmpty()) {
|
||||
return pgpSecretKeys;
|
||||
}
|
||||
|
||||
throw new PGPException("No private key found in stream!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the signature that will be used to PGP sign data
|
||||
*
|
||||
* @param secretKeys
|
||||
* these are the secret keys
|
||||
* @param password
|
||||
* this is the password to unlock the secret key
|
||||
*
|
||||
* @return the signature used to sign data
|
||||
*
|
||||
* @throws PGPException
|
||||
*/
|
||||
private static
|
||||
PGPSignatureGenerator createSignature(List<PGPSecretKey> secretKeys,
|
||||
char[] password,
|
||||
int signatureType,
|
||||
boolean generateUserIdSubPacket) throws PGPException {
|
||||
|
||||
PGPSecretKey secretKey = null;
|
||||
for (int i = 0; i < secretKeys.size(); i++) {
|
||||
secretKey = secretKeys.get(i);
|
||||
|
||||
// we ONLY want the signing master key
|
||||
if (!secretKey.isSigningKey() || !secretKey.isMasterKey()) {
|
||||
secretKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (secretKey == null) {
|
||||
throw new PGPException("Secret key is not the signing master key");
|
||||
}
|
||||
|
||||
// System.err.println("Signing key = " + tmpKey.isSigningKey() +", Master key = " + tmpKey.isMasterKey() + ", UserId = " +
|
||||
// userId );
|
||||
|
||||
if (password == null) {
|
||||
password = new char[0];
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor build = new BcPBESecretKeyDecryptorBuilder(digestCalculatorProvider).build(password);
|
||||
|
||||
SecureRandom random = new SecureRandom();
|
||||
BcPGPContentSignerBuilder bcPGPContentSignerBuilder = new BcPGPContentSignerBuilder(secretKey.getPublicKey()
|
||||
.getAlgorithm(),
|
||||
PGPUtil.SHA1).setSecureRandom(random);
|
||||
|
||||
PGPSignatureGenerator signature = new PGPSignatureGenerator(bcPGPContentSignerBuilder);
|
||||
signature.init(signatureType, secretKey.extractPrivateKey(build));
|
||||
|
||||
Iterator userIds = secretKey.getPublicKey()
|
||||
.getUserIDs();
|
||||
|
||||
// use the first userId that matches
|
||||
if (userIds.hasNext()) {
|
||||
if (generateUserIdSubPacket) {
|
||||
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator();
|
||||
subpacketGenerator.addSignerUserID(false, (String) userIds.next());
|
||||
signature.setHashedSubpackets(subpacketGenerator.generate());
|
||||
}
|
||||
else {
|
||||
signature.setHashedSubpackets(null);
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
else {
|
||||
throw new PGPException("Did not find specified userId");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Decode a PGP public key block and return the keyring it represents.
|
||||
*/
|
||||
public static
|
||||
PGPPublicKeyRing getKeyring(InputStream keyBlockStream) throws IOException {
|
||||
|
||||
BcKeyFingerprintCalculator keyfp = new BcKeyFingerprintCalculator();
|
||||
|
||||
// PGPUtil.getDecoderStream() will detect ASCII-armor automatically and decode it,
|
||||
// the PGPObject factory then knows how to read all the data in the encoded stream
|
||||
PGPObjectFactory factory = new PGPObjectFactory(PGPUtil.getDecoderStream(keyBlockStream), keyfp);
|
||||
|
||||
// these files should really just have one object in them, and that object should be a PGPPublicKeyRing.
|
||||
Object o = factory.nextObject();
|
||||
if (o instanceof PGPPublicKeyRing) {
|
||||
return (PGPPublicKeyRing) o;
|
||||
}
|
||||
throw new IllegalArgumentException("Input stream does not contain a PGP Public Key");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first encryption key from the given keyring.
|
||||
*/
|
||||
public static
|
||||
PGPPublicKey getEncryptionKey(PGPPublicKeyRing keyRing) {
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// iterate over the keys on the ring, look for one which is suitable for encryption.
|
||||
Iterator keys = keyRing.getPublicKeys();
|
||||
PGPPublicKey key;
|
||||
while (keys.hasNext()) {
|
||||
key = (PGPPublicKey) keys.next();
|
||||
if (key.isEncryptionKey()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first decryption key from the given keyring.
|
||||
*/
|
||||
public
|
||||
PGPSecretKey getDecryptionKey(PGPSecretKeyRing keyRing) {
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// iterate over the keys on the ring, look for one which is suitable for encryption.
|
||||
Iterator keys = keyRing.getSecretKeys();
|
||||
PGPSecretKey key;
|
||||
while (keys.hasNext()) {
|
||||
key = (PGPSecretKey) keys.next();
|
||||
if (key.isMasterKey()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt plaintext message using public key from publickeyFile.
|
||||
*
|
||||
* @param message
|
||||
* the message
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
private
|
||||
String encrypt(InputStream publicKeyInputStream, String message) throws PGPException, IOException, NoSuchProviderException {
|
||||
// find the PGP key in the file
|
||||
PGPPublicKey publicKey = findPublicGPGKey(publicKeyInputStream);
|
||||
|
||||
if (publicKey == null) {
|
||||
System.err.println("Did not find public GPG key");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Encode the string into bytes using utf-8
|
||||
byte[] utf8Bytes = message.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
ByteArrayOutputStream compressedOutput = new ByteArrayOutputStream();
|
||||
|
||||
// compress bytes with zip
|
||||
PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
|
||||
|
||||
// the reason why we compress here is GPG not being able to decrypt our message input but if we do not compress.
|
||||
// I guess pkzip compression also encodes only to GPG-friendly characters.
|
||||
PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
|
||||
try {
|
||||
OutputStream literalDataOutput = literalDataGenerator.open(compressedOutput,
|
||||
PGPLiteralData.BINARY,
|
||||
"_CONSOLE",
|
||||
utf8Bytes.length,
|
||||
new Date());
|
||||
// update bytes in the stream
|
||||
literalDataOutput.write(utf8Bytes);
|
||||
} catch (IOException e) {
|
||||
// catch but close the streams in finally
|
||||
throw e;
|
||||
} finally {
|
||||
compressedDataGenerator.close();
|
||||
IO.close(compressedOutput);
|
||||
}
|
||||
|
||||
SecureRandom random = new SecureRandom();
|
||||
|
||||
// now we have zip-compressed bytes
|
||||
byte[] compressedBytes = compressedOutput.toByteArray();
|
||||
|
||||
BcPGPDataEncryptorBuilder bcPGPDataEncryptorBuilder = new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
|
||||
.setWithIntegrityPacket(true)
|
||||
.setSecureRandom(random);
|
||||
|
||||
PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(bcPGPDataEncryptorBuilder);
|
||||
|
||||
// use public key to encrypt data
|
||||
|
||||
BcPublicKeyKeyEncryptionMethodGenerator encKeyGen = new BcPublicKeyKeyEncryptionMethodGenerator(publicKey)
|
||||
.setSecureRandom(random);
|
||||
|
||||
encryptedDataGenerator.addMethod(encKeyGen);
|
||||
|
||||
// literalDataOutput --> compressedOutput --> ArmoredOutputStream --> ByteArrayOutputStream
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream armoredOut = new ArmoredOutputStream(byteArrayOutputStream);
|
||||
OutputStream encryptedOutput = null;
|
||||
try {
|
||||
encryptedOutput = encryptedDataGenerator.open(armoredOut, compressedBytes.length);
|
||||
encryptedOutput.write(compressedBytes);
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (PGPException e) {
|
||||
throw e;
|
||||
} finally {
|
||||
IO.close(encryptedOutput);
|
||||
IO.close(armoredOut);
|
||||
}
|
||||
String encrypted = new String(byteArrayOutputStream.toByteArray());
|
||||
|
||||
System.err.println("Message: " + message);
|
||||
System.err.println("Encrypted: " + encrypted);
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find public gpg key in InputStream.
|
||||
*
|
||||
* @param inputStream
|
||||
* the input stream
|
||||
*
|
||||
* @return the PGP public key
|
||||
*/
|
||||
private static
|
||||
PGPPublicKey findPublicGPGKey(InputStream inputStream) throws IOException, PGPException {
|
||||
|
||||
// get all key rings in the input stream
|
||||
PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(inputStream), fingerprintCalculator);
|
||||
|
||||
System.err.println("key ring size: " + publicKeyRingCollection.size());
|
||||
|
||||
Iterator<PGPPublicKeyRing> keyRingIter = publicKeyRingCollection.getKeyRings();
|
||||
|
||||
// iterate over keyrings
|
||||
while (keyRingIter.hasNext()) {
|
||||
PGPPublicKeyRing keyRing = keyRingIter.next();
|
||||
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
|
||||
// iterate over public keys in the key ring
|
||||
while (keyIter.hasNext()) {
|
||||
PGPPublicKey tmpKey = keyIter.next();
|
||||
|
||||
if (tmpKey == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
Iterator<String> userIDs = tmpKey.getUserIDs();
|
||||
ArrayList<String> strings = new ArrayList<String>();
|
||||
while (userIDs.hasNext()) {
|
||||
String next = userIDs.next();
|
||||
strings.add(next);
|
||||
}
|
||||
|
||||
System.err.println(
|
||||
"Encryption key = " + tmpKey.isEncryptionKey() + ", Master key = " + tmpKey.isMasterKey() + ", UserId = " +
|
||||
strings);
|
||||
|
||||
// we need a master encryption key
|
||||
if (tmpKey.isEncryptionKey() && tmpKey.isMasterKey()) {
|
||||
return tmpKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new PGPException("No public key found!");
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static
|
||||
void verify(final InputStream publicKeyInputStream, final byte[] signature) throws Exception {
|
||||
PGPPublicKey publicKey = findPublicGPGKey(publicKeyInputStream);
|
||||
|
||||
String text = new String(signature);
|
||||
|
||||
Pattern regex = Pattern.compile(
|
||||
"-----BEGIN PGP SIGNED MESSAGE-----\\r?\\n.*?\\r?\\n\\r?\\n(.*)\\r?\\n(-----BEGIN PGP SIGNATURE-----\\r?\\n.*-----END PGP SIGNATURE-----)",
|
||||
Pattern.CANON_EQ | Pattern.DOTALL);
|
||||
Matcher regexMatcher = regex.matcher(text);
|
||||
if (regexMatcher.find()) {
|
||||
String dataText = regexMatcher.group(1);
|
||||
String signText = regexMatcher.group(2);
|
||||
|
||||
ByteArrayInputStream dataIn = new ByteArrayInputStream(dataText.getBytes("UTF8"));
|
||||
ByteArrayInputStream signIn = new ByteArrayInputStream(signText.getBytes("UTF8"));
|
||||
|
||||
|
||||
InputStream signIn2 = PGPUtil.getDecoderStream(signIn);
|
||||
|
||||
PGPObjectFactory pgpFact = new PGPObjectFactory(signIn2, new BcKeyFingerprintCalculator());
|
||||
PGPSignatureList p3 = null;
|
||||
|
||||
Object o;
|
||||
|
||||
try {
|
||||
o = pgpFact.nextObject();
|
||||
if (o == null) {
|
||||
throw new Exception();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new Exception("Invalid input data");
|
||||
}
|
||||
|
||||
if (o instanceof PGPCompressedData) {
|
||||
PGPCompressedData c1 = (PGPCompressedData) o;
|
||||
|
||||
pgpFact = new PGPObjectFactory(c1.getDataStream(), new BcKeyFingerprintCalculator());
|
||||
|
||||
p3 = (PGPSignatureList) pgpFact.nextObject();
|
||||
}
|
||||
else {
|
||||
p3 = (PGPSignatureList) o;
|
||||
}
|
||||
|
||||
|
||||
// PGPSignature sig = p3.get(0);
|
||||
// PGPPublicKey key = KeyRing.getPublicKeyByID(sig.getKeyID());
|
||||
//
|
||||
// if (key == null)
|
||||
// throw new Exception("Cannot find key 0x" + Integer.toHexString((int) sig.getKeyID()).toUpperCase() + " in the pubring");
|
||||
//
|
||||
// sig.initVerify(key, "BC");
|
||||
//
|
||||
// while ((ch = dataIn.read()) >= 0) {
|
||||
// sig.update((byte) ch); //TODO migliorabile con byte[]
|
||||
// }
|
||||
//
|
||||
// if (sig.verify())
|
||||
// return new PrintablePGPPublicKey(key).toString();
|
||||
// else
|
||||
// return null;
|
||||
|
||||
// return verifyFile(dataIn, signIn);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
CryptoPGP() {
|
||||
}
|
||||
|
||||
public static
|
||||
void main(String[] args) throws Exception {
|
||||
InputStream privateKeyInputStream = new FileInputStream(new File("/home/user/dorkbox/sonatype_private.key"));
|
||||
|
||||
byte[] textBytes = "hello".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
byte[] bytes = CryptoPGP.signGpgCompatible(privateKeyInputStream, "Dorkbox <sonatype@dorkbox.com>", new char[0], textBytes);
|
||||
|
||||
// String s = new String(hello);
|
||||
// String s1 = s.replaceAll("\n", "\r\n");
|
||||
// byte[] bytes = s1.getBytes(OS.UTF_8);
|
||||
|
||||
//
|
||||
// String signed = new String(bytes);
|
||||
//
|
||||
// System.err.println("Message: " + new String(messageAsUtf8Bytes));
|
||||
// System.err.println("Signature: " + signed);
|
||||
//
|
||||
// return bytes;
|
||||
|
||||
// String s2 = new String(bytes);
|
||||
|
||||
|
||||
// InputStream publicKeyInputStream = new FileInputStream(new File("/home/user/dorkbox/sonatype_public.key"));
|
||||
// cryptoPGP.verify(publicKeyInputStream, hello);
|
||||
|
||||
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(new File("/home/user/dorkbox/hello2.txt"));
|
||||
fileOutputStream.write(textBytes);
|
||||
fileOutputStream.flush();
|
||||
IO.close(fileOutputStream);
|
||||
|
||||
|
||||
FileOutputStream fileOutputStream1 = new FileOutputStream(new File("/home/user/dorkbox/hello2.txt.asc"));
|
||||
fileOutputStream1.write(bytes);
|
||||
fileOutputStream1.flush();
|
||||
IO.close(fileOutputStream1);
|
||||
}
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricBlockCipher;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.PSSSigner;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* This is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||
*/
|
||||
@Deprecated
|
||||
public final
|
||||
class CryptoRSA {
|
||||
public static
|
||||
AsymmetricCipherKeyPair generateKeyPair(SecureRandom secureRandom, int keyLength) {
|
||||
RSAKeyPairGenerator keyGen = new RSAKeyPairGenerator();
|
||||
RSAKeyGenerationParameters params = new RSAKeyGenerationParameters(new BigInteger("65537"), // public exponent
|
||||
secureRandom, //pnrg
|
||||
keyLength, // key length
|
||||
8); //the number of iterations of the Miller-Rabin primality test.
|
||||
keyGen.init(params);
|
||||
return keyGen.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA encrypt using public key A, and sign data with private key B.
|
||||
* <p/>
|
||||
* byte[0][] = encrypted data byte[1][] = signature
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[][] if error
|
||||
*/
|
||||
public static
|
||||
byte[][] encryptAndSign(AsymmetricBlockCipher rsaEngine,
|
||||
Digest digest,
|
||||
RSAKeyParameters rsaPublicKeyA,
|
||||
RSAPrivateCrtKeyParameters rsaPrivateKeyB,
|
||||
byte[] bytes,
|
||||
Logger logger) {
|
||||
if (bytes.length == 0) {
|
||||
return new byte[0][0];
|
||||
}
|
||||
|
||||
byte[] encryptBytes = encrypt(rsaEngine, rsaPublicKeyA, bytes, logger);
|
||||
|
||||
if (encryptBytes.length == 0) {
|
||||
return new byte[0][0];
|
||||
}
|
||||
|
||||
// now sign it.
|
||||
PSSSigner signer = new PSSSigner(rsaEngine, digest, digest.getDigestSize());
|
||||
|
||||
byte[] signatureRSA = CryptoRSA.sign(signer, rsaPrivateKeyB, encryptBytes, logger);
|
||||
|
||||
if (signatureRSA.length == 0) {
|
||||
return new byte[0][0];
|
||||
}
|
||||
|
||||
byte[][] total = new byte[2][];
|
||||
total[0] = encryptBytes;
|
||||
total[1] = signatureRSA;
|
||||
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA verify data with public key B, and decrypt using private key A.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decryptAndVerify(AsymmetricBlockCipher rsaEngine,
|
||||
Digest digest,
|
||||
RSAKeyParameters rsaPublicKeyA,
|
||||
RSAPrivateCrtKeyParameters rsaPrivateKeyB,
|
||||
byte[] encryptedData,
|
||||
byte[] signature,
|
||||
Logger logger) {
|
||||
if (encryptedData.length == 0 || signature.length == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
// verify encrypted data.
|
||||
PSSSigner signer = new PSSSigner(rsaEngine, digest, digest.getDigestSize());
|
||||
|
||||
boolean verify = verify(signer, rsaPublicKeyA, signature, encryptedData);
|
||||
if (!verify) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
return decrypt(rsaEngine, rsaPrivateKeyB, encryptedData, logger);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA encrypts data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] encrypt(AsymmetricBlockCipher rsaEngine, RSAKeyParameters rsaPublicKey, byte[] bytes, Logger logger) {
|
||||
rsaEngine.init(true, rsaPublicKey);
|
||||
|
||||
try {
|
||||
int inputBlockSize = rsaEngine.getInputBlockSize();
|
||||
if (inputBlockSize < bytes.length) {
|
||||
int outSize = rsaEngine.getOutputBlockSize();
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int realsize = (int) Math.round(bytes.length / (outSize * 1.0D) + 0.5);
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(outSize * realsize);
|
||||
|
||||
int position = 0;
|
||||
|
||||
while (position < bytes.length) {
|
||||
int size = Math.min(inputBlockSize, bytes.length - position);
|
||||
|
||||
byte[] block = rsaEngine.processBlock(bytes, position, size);
|
||||
buffer.put(block, 0, block.length);
|
||||
|
||||
position += size;
|
||||
}
|
||||
|
||||
|
||||
return buffer.array();
|
||||
|
||||
}
|
||||
else {
|
||||
return rsaEngine.processBlock(bytes, 0, bytes.length);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform RSA cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA decrypt data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] decrypt(AsymmetricBlockCipher rsaEngine, RSAPrivateCrtKeyParameters rsaPrivateKey, byte[] bytes, Logger logger) {
|
||||
rsaEngine.init(false, rsaPrivateKey);
|
||||
|
||||
try {
|
||||
int inputBlockSize = rsaEngine.getInputBlockSize();
|
||||
if (inputBlockSize < bytes.length) {
|
||||
int outSize = rsaEngine.getOutputBlockSize();
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int realsize = (int) Math.round(bytes.length / (outSize * 1.0D) + 0.5);
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(outSize * realsize);
|
||||
|
||||
int position = 0;
|
||||
|
||||
while (position < bytes.length) {
|
||||
int size = Math.min(inputBlockSize, bytes.length - position);
|
||||
|
||||
byte[] block = rsaEngine.processBlock(bytes, position, size);
|
||||
buffer.write(block, 0, block.length);
|
||||
|
||||
position += size;
|
||||
}
|
||||
|
||||
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
else {
|
||||
return rsaEngine.processBlock(bytes, 0, bytes.length);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform RSA cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA sign data with a specified key.
|
||||
*
|
||||
* @param logger
|
||||
* may be null, if no log output is necessary
|
||||
*
|
||||
* @return empty byte[] if error
|
||||
*/
|
||||
public static
|
||||
byte[] sign(PSSSigner signer, RSAPrivateCrtKeyParameters rsaPrivateKey, byte[] mesg, Logger logger) {
|
||||
signer.init(true, rsaPrivateKey);
|
||||
signer.update(mesg, 0, mesg.length);
|
||||
|
||||
try {
|
||||
return signer.generateSignature();
|
||||
} catch (Exception e) {
|
||||
if (logger != null) {
|
||||
logger.error("Unable to perform RSA cipher.", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA verify data with a specified key.
|
||||
*/
|
||||
public static
|
||||
boolean verify(PSSSigner signer, RSAKeyParameters rsaPublicKey, byte[] sig, byte[] mesg) {
|
||||
signer.init(false, rsaPublicKey);
|
||||
signer.update(mesg, 0, mesg.length);
|
||||
|
||||
return signer.verifySignature(sig);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
public static
|
||||
boolean compare(RSAKeyParameters publicA, RSAKeyParameters publicB) {
|
||||
if (!publicA.getExponent()
|
||||
.equals(publicB.getExponent())) {
|
||||
return false;
|
||||
}
|
||||
if (!publicA.getModulus()
|
||||
.equals(publicB.getModulus())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
public static
|
||||
boolean compare(RSAPrivateCrtKeyParameters private1, RSAPrivateCrtKeyParameters private2) {
|
||||
if (!private1.getModulus()
|
||||
.equals(private2.getModulus())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getExponent()
|
||||
.equals(private2.getExponent())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getDP()
|
||||
.equals(private2.getDP())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getDQ()
|
||||
.equals(private2.getDQ())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getP()
|
||||
.equals(private2.getP())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getPublicExponent()
|
||||
.equals(private2.getPublicExponent())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getQ()
|
||||
.equals(private2.getQ())) {
|
||||
return false;
|
||||
}
|
||||
if (!private1.getQInv()
|
||||
.equals(private2.getQInv())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
CryptoRSA() {
|
||||
}
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* An implementation of the <a href="http://www.tarsnap.com/scrypt/scrypt.pdf"/>scrypt</a> key derivation function.
|
||||
*/
|
||||
public final
|
||||
class CryptoSCrypt {
|
||||
/**
|
||||
* Hash the supplied plaintext password and generate output using default parameters
|
||||
* <p/>
|
||||
* The password chars are no longer valid after this call
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
*/
|
||||
public static
|
||||
String encrypt(char[] password) {
|
||||
return encrypt(password, 16384, 32, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the supplied plaintext password and generate output using default parameters
|
||||
* <p/>
|
||||
* The password chars are no longer valid after this call
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
* @param salt
|
||||
* Salt parameter
|
||||
*/
|
||||
public static
|
||||
String encrypt(char[] password, byte[] salt) {
|
||||
return encrypt(password, salt, 16384, 128, 1, 64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the supplied plaintext password and generate output.
|
||||
* <p/>
|
||||
* The password chars are no longer valid after this call
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
* @param N
|
||||
* CPU cost parameter.
|
||||
* @param r
|
||||
* Memory cost parameter.
|
||||
* @param p
|
||||
* Parallelization parameter.
|
||||
*
|
||||
* @return The hashed password.
|
||||
*/
|
||||
public static
|
||||
String encrypt(char[] password, int N, int r, int p) {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
byte[] salt = new byte[32];
|
||||
secureRandom.nextBytes(salt);
|
||||
|
||||
return encrypt(password, salt, N, r, p, 64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the supplied plaintext password and generate output.
|
||||
* <p/>
|
||||
* The password chars are no longer valid after this call
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
* @param salt
|
||||
* Salt parameter
|
||||
* @param N
|
||||
* CPU cost parameter.
|
||||
* @param r
|
||||
* Memory cost parameter.
|
||||
* @param p
|
||||
* Parallelization parameter.
|
||||
* @param dkLen
|
||||
* Intended length of the derived key.
|
||||
*
|
||||
* @return The hashed password.
|
||||
*/
|
||||
public static
|
||||
String encrypt(char[] password, byte[] salt, int N, int r, int p, int dkLen) {
|
||||
// Note: this saves the char array in UTF-16 format of bytes.
|
||||
// can't use password after this as it's been changed to '*'
|
||||
byte[] passwordBytes = Crypto.charToBytesPassword_UTF16(password);
|
||||
|
||||
byte[] derived = encrypt(passwordBytes, salt, N, r, p, dkLen);
|
||||
|
||||
String params = Integer.toString(log2(N) << 16 | r << 8 | p, 16);
|
||||
|
||||
@SuppressWarnings("StringBufferReplaceableByString")
|
||||
StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2);
|
||||
sb.append("$s0$")
|
||||
.append(params)
|
||||
.append('$');
|
||||
sb.append(Base64.getEncoder().encodeToString(salt))
|
||||
.append('$');
|
||||
sb.append(Base64.getEncoder().encodeToString(derived));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the supplied plaintext password to a hashed password.
|
||||
*
|
||||
* @param password
|
||||
* Plaintext password.
|
||||
* @param hashed
|
||||
* scrypt hashed password.
|
||||
*
|
||||
* @return true if password matches hashed value.
|
||||
*/
|
||||
public static
|
||||
boolean verify(char[] password, String hashed) {
|
||||
// Note: this saves the char array in UTF-16 format of bytes.
|
||||
// can't use password after this as it's been changed to '*'
|
||||
byte[] passwordBytes = Crypto.charToBytesPassword_UTF16(password);
|
||||
|
||||
String[] parts = hashed.split("\\$");
|
||||
|
||||
if (parts.length != 5 || !parts[1].equals("s0")) {
|
||||
throw new IllegalArgumentException("Invalid hashed value");
|
||||
}
|
||||
|
||||
int params = Integer.parseInt(parts[2], 16);
|
||||
byte[] salt = Base64.getDecoder().decode(parts[3]);
|
||||
byte[] derived0 = Base64.getDecoder().decode(parts[4]);
|
||||
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
int N = (int) Math.pow(2, params >> 16 & 0xFF);
|
||||
int r = params >> 8 & 0xFF;
|
||||
int p = params & 0xFF;
|
||||
|
||||
int length = derived0.length;
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] derived1 = encrypt(passwordBytes, salt, N, r, p, length);
|
||||
|
||||
if (length != derived1.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
result |= derived0[i] ^ derived1[i];
|
||||
}
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
private static
|
||||
int log2(int n) {
|
||||
int log = 0;
|
||||
if ((n & 0xFFFF0000) != 0) {
|
||||
n >>>= 16;
|
||||
log = 16;
|
||||
}
|
||||
if (n >= 256) {
|
||||
n >>>= 8;
|
||||
log += 8;
|
||||
}
|
||||
if (n >= 16) {
|
||||
n >>>= 4;
|
||||
log += 4;
|
||||
}
|
||||
if (n >= 4) {
|
||||
n >>>= 2;
|
||||
log += 2;
|
||||
}
|
||||
return log + (n >>> 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure Java implementation of the <a href="http://www.tarsnap.com/scrypt/scrypt.pdf"/>scrypt KDF</a>.
|
||||
*
|
||||
* @param password
|
||||
* Password.
|
||||
* @param salt
|
||||
* Salt.
|
||||
* @param N
|
||||
* CPU cost parameter.
|
||||
* @param r
|
||||
* Memory cost parameter.
|
||||
* @param p
|
||||
* Parallelization parameter.
|
||||
* @param dkLen
|
||||
* Intended length of the derived key.
|
||||
*
|
||||
* @return The derived key.
|
||||
*/
|
||||
public static
|
||||
byte[] encrypt(byte[] password, byte[] salt, int N, int r, int p, int dkLen) {
|
||||
if (N == 0 || (N & N - 1) != 0) {
|
||||
throw new IllegalArgumentException("N must be > 0 and a power of 2");
|
||||
}
|
||||
|
||||
if (N > Integer.MAX_VALUE / 128 / r) {
|
||||
throw new IllegalArgumentException("Parameter N is too large");
|
||||
}
|
||||
if (r > Integer.MAX_VALUE / 128 / p) {
|
||||
throw new IllegalArgumentException("Parameter r is too large");
|
||||
}
|
||||
|
||||
try {
|
||||
return org.bouncycastle.crypto.generators.SCrypt.generate(password, salt, N, r, p, dkLen);
|
||||
} finally {
|
||||
// now zero out the bytes in password.
|
||||
Arrays.fill(password, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
private
|
||||
CryptoSCrypt() {
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,336 +0,0 @@
|
|||
package dorkbox.util.crypto
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.security.GeneralSecurityException
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.IllegalBlockSizeException
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
object OpenSSLDecryptor {
|
||||
private val INDEX_KEY = 0
|
||||
private val INDEX_IV = 1
|
||||
private val ITERATIONS = 1
|
||||
|
||||
private val ARG_INDEX_FILENAME = 0
|
||||
private val ARG_INDEX_PASSWORD = 1
|
||||
|
||||
private val SALT_OFFSET = 8
|
||||
private val SALT_SIZE = 8
|
||||
private val CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE
|
||||
|
||||
private val KEY_SIZE_BITS = 256
|
||||
|
||||
/**
|
||||
* Thanks go to Ola Bini for releasing this source on his blog.
|
||||
* The source was obtained from [here](http://olabini.com/blog/tag/evp_bytestokey/) .
|
||||
*/
|
||||
fun EVP_BytesToKey(key_len: Int, iv_len: Int, md: MessageDigest, salt: ByteArray?, data: ByteArray?, count: Int): Array<ByteArray> {
|
||||
val key = ByteArray(key_len)
|
||||
var key_ix = 0
|
||||
|
||||
val iv = ByteArray(iv_len)
|
||||
var iv_ix = 0
|
||||
|
||||
val both = arrayOf(key, iv)
|
||||
if (data == null) {
|
||||
return both
|
||||
}
|
||||
|
||||
var md_buf: ByteArray? = null
|
||||
var nkey = key_len
|
||||
var niv = iv_len
|
||||
var i: Int
|
||||
|
||||
var addmd = 0
|
||||
|
||||
|
||||
while (true) {
|
||||
md.reset()
|
||||
if (addmd++ > 0) {
|
||||
md.update(md_buf!!)
|
||||
}
|
||||
|
||||
md.update(data)
|
||||
if (null != salt) {
|
||||
md.update(salt, 0, 8)
|
||||
}
|
||||
|
||||
md_buf = md.digest()
|
||||
i = 1
|
||||
while (i < count) {
|
||||
md.reset()
|
||||
md.update(md_buf!!)
|
||||
md_buf = md.digest()
|
||||
i++
|
||||
}
|
||||
|
||||
i = 0
|
||||
if (nkey > 0) {
|
||||
while (true) {
|
||||
if (nkey == 0) {
|
||||
break
|
||||
}
|
||||
if (i == md_buf!!.size) {
|
||||
break
|
||||
}
|
||||
key[key_ix++] = md_buf[i]
|
||||
nkey--
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if (niv > 0 && i != md_buf!!.size) {
|
||||
while (true) {
|
||||
if (niv == 0) {
|
||||
break
|
||||
}
|
||||
if (i == md_buf.size) {
|
||||
break
|
||||
}
|
||||
iv[iv_ix++] = md_buf[i]
|
||||
niv--
|
||||
i++
|
||||
}
|
||||
}
|
||||
if (nkey == 0 && niv == 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
i = 0
|
||||
while (i < md_buf!!.size) {
|
||||
md_buf[i] = 0
|
||||
i++
|
||||
}
|
||||
|
||||
return both
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
// /usr/bin/openssl enc -d -aes-256-cbc -md sha256 -in update_file.encrypted -out update_file.bin -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
|
||||
// NON-PBKDF2, WITH BASE64
|
||||
// openssl enc -aes-256-cbc -a -salt -md sha256 -in password.txt -out password.txt.enc -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
// openssl enc -aes-256-cbc -a -d -md sha256 -in password.txt.enc -out password.txt.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01 && cat password.txt.new
|
||||
|
||||
// NON-PBKDF2, WITHOUT BASE64
|
||||
// openssl enc -aes-256-cbc -salt -md sha256 -in password.txt -out password.txt.enc -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
// openssl enc -aes-256-cbc -d -md sha256 -in password.txt.enc -out password.txt.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01 && cat password.txt.new
|
||||
|
||||
|
||||
|
||||
// PBKDF2 (NOT WORKING)
|
||||
// openssl aes-256-cbc -a -salt -pbkdf2 -iter 1 -md sha256 -in password.txt -out password.txt.enc -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
// openssl aes-256-cbc -d -a -md sha256 -pbkdf2 -iter 1 -in password.txt.enc -out password.txt.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01 && cat password.txt.new
|
||||
|
||||
val decrypt = false
|
||||
try {
|
||||
val password = "xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01"
|
||||
val passwordBytes = password.toByteArray(Charsets.US_ASCII)
|
||||
|
||||
|
||||
|
||||
// openssl enc -aes-256-cbc -d -md sha256 -in install_2019.1.bin.enc -out install_2019.1.bin.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
|
||||
// val plaintextFileName = "build/install_2019.1.bin"
|
||||
// val encryptedFileName = "build/install_2019.1.bin.enc"
|
||||
// val fileOutputStream = FileOutputStream(File(encryptedFileName))
|
||||
// OpenSSLPBEOutputStream(fileOutputStream, password).use { outputStream ->
|
||||
// Files.copy(Path.of(plaintextFileName), outputStream)
|
||||
// }
|
||||
//
|
||||
// if (true) {
|
||||
// return
|
||||
// }
|
||||
|
||||
|
||||
val plaintextFileName = "password.txt"
|
||||
val encryptedFileName = "password.txt.enc"
|
||||
if (decrypt) {
|
||||
// --- read base 64 encoded file ---
|
||||
|
||||
val encryptedFile = File(encryptedFileName).absoluteFile
|
||||
|
||||
// this is WITH BASE64 (with openssl -a)
|
||||
// val lines = Files.readAllLines(encryptedFile.toPath(), Charsets.US_ASCII)
|
||||
// val sb = StringBuilder()
|
||||
// for (line in lines) {
|
||||
// sb.append(line.trim())
|
||||
// }
|
||||
// val dataBase64 = sb.toString()
|
||||
// val headerSaltAndCipherText = Base64.getDecoder().decode(dataBase64) // when base64 encoded
|
||||
|
||||
// this is NOT base64
|
||||
val headerSaltAndCipherText = encryptedFile.readBytes()
|
||||
|
||||
// --- extract salt & encrypted ---
|
||||
|
||||
// header is "Salted__", ASCII encoded, if salt is being used (the default)
|
||||
val salt = Arrays.copyOfRange(headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE)
|
||||
val encrypted = Arrays.copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.size)
|
||||
|
||||
// --- specify cipher and digest for EVP_BytesToKey method ---
|
||||
|
||||
val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
// --- create key and IV ---
|
||||
|
||||
// the IV is useless, OpenSSL might as well have use zero's
|
||||
// val keyAndIV = EVP_BytesToKey(KEY_SIZE_BITS / java.lang.Byte.SIZE, aesCBC.blockSize, md, salt, passString.toByteArray(ASCII), ITERATIONS)
|
||||
// val key = SecretKeySpec(keyAndIV[INDEX_KEY], "AES")
|
||||
// val iv = IvParameterSpec(keyAndIV[INDEX_IV])
|
||||
/////////////////////
|
||||
// val openssl = OpenSSLPBEParametersGenerator()
|
||||
// openssl.init(passString.toByteArray(Charsets.US_ASCII), salt)
|
||||
// val keyAndIV = openssl.generateDerivedParameters(256)
|
||||
//
|
||||
// val keyBytes = Arrays.copyOfRange(keyAndIV, 0, 32)
|
||||
// val ivBytes = Arrays.copyOfRange(keyAndIV, 32, 48)
|
||||
//
|
||||
// val key = SecretKeySpec(keyBytes, "AES")
|
||||
// val iv = IvParameterSpec(ivBytes)
|
||||
//////////////////////
|
||||
md.update(passwordBytes)
|
||||
md.update(salt)
|
||||
|
||||
var hash = md.digest()
|
||||
var keyAndIV = hash.clone()
|
||||
|
||||
// 1 round
|
||||
md.update(hash)
|
||||
md.update(passwordBytes)
|
||||
md.update(salt)
|
||||
|
||||
hash = md.digest()
|
||||
keyAndIV = concat(keyAndIV, hash)
|
||||
|
||||
val keyBytes = Arrays.copyOfRange(keyAndIV, 0, 32)
|
||||
val ivBytes = Arrays.copyOfRange(keyAndIV, 32, 48)
|
||||
|
||||
val key = SecretKeySpec(keyBytes, "AES")
|
||||
val iv = IvParameterSpec(ivBytes)
|
||||
///////////////////
|
||||
// val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
// val spec = PBEKeySpec(passString.toCharArray(), salt, ITERATIONS, KEY_SIZE_BITS)
|
||||
// val tmp = factory.generateSecret(spec)
|
||||
// val key = SecretKeySpec(tmp.encoded, "AES")
|
||||
//
|
||||
//
|
||||
// val ivBytes = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
// val iv = IvParameterSpec(ivBytes)
|
||||
/////////////////////////
|
||||
|
||||
// --- initialize cipher instance and decrypt ---
|
||||
aesCBC.init(Cipher.DECRYPT_MODE, key, iv)
|
||||
val decrypted = aesCBC.doFinal(encrypted)
|
||||
val answer = String(decrypted, Charsets.UTF_8)
|
||||
println(answer)
|
||||
}
|
||||
else {
|
||||
// read plaintext file
|
||||
val plainTextFile = File(plaintextFileName).absoluteFile
|
||||
val data= plainTextFile.readBytes()
|
||||
|
||||
// --- create salt ---
|
||||
val salt = ByteArray(8)
|
||||
val secureRandom = SecureRandom()
|
||||
secureRandom.nextBytes(salt)
|
||||
|
||||
// --- specify cipher and digest for EVP_BytesToKey method ---
|
||||
|
||||
val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
|
||||
//////////////////////////////////////////
|
||||
// the IV is useless, OpenSSL might as well have use zero's
|
||||
// val keyAndIV = EVP_BytesToKey(KEY_SIZE_BITS / java.lang.Byte.SIZE, aesCBC.blockSize, md, salt,
|
||||
// passString.toByteArray(Charsets.US_ASCII), ITERATIONS)
|
||||
// val key = SecretKeySpec(keyAndIV[INDEX_KEY], "AES")
|
||||
// val iv = IvParameterSpec(keyAndIV[INDEX_IV])
|
||||
//////////////////////////////////////////
|
||||
md.update(passwordBytes)
|
||||
md.update(salt)
|
||||
|
||||
var hash = md.digest()
|
||||
var keyAndIV = hash.clone()
|
||||
|
||||
// 1 round
|
||||
md.update(hash)
|
||||
md.update(passwordBytes)
|
||||
md.update(salt)
|
||||
|
||||
hash = md.digest()
|
||||
keyAndIV = concat(keyAndIV, hash)
|
||||
|
||||
val keyBytes = Arrays.copyOfRange(keyAndIV, 0, 32)
|
||||
val ivBytes = Arrays.copyOfRange(keyAndIV, 32, 48)
|
||||
|
||||
val key = SecretKeySpec(keyBytes, "AES")
|
||||
val iv = IvParameterSpec(ivBytes)
|
||||
//////////////////////////////////////////
|
||||
// val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
// val spec = PBEKeySpec(passString.toCharArray(), salt, ITERATIONS, KEY_SIZE_BITS)
|
||||
// val tmp = factory.generateSecret(spec)
|
||||
// val key = SecretKeySpec(tmp.encoded, "AES")
|
||||
//
|
||||
//
|
||||
// val ivBytes = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
// val iv = IvParameterSpec(ivBytes)
|
||||
/////////////////////////
|
||||
|
||||
|
||||
// --- initialize cipher instance and encrypt ---
|
||||
|
||||
aesCBC.init(Cipher.ENCRYPT_MODE, key, iv)
|
||||
val encrypted = aesCBC.doFinal(data)
|
||||
|
||||
// val cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.ENCRYPT_MODE)
|
||||
// val encrypted = cipher.doFinal(data)
|
||||
|
||||
// "Salted__" + salt + encrypted
|
||||
val a1 = "Salted__".toByteArray(Charsets.US_ASCII)
|
||||
|
||||
val finalEncrypted = concat(concat(a1, salt), encrypted)
|
||||
|
||||
val encryptedFile = File(encryptedFileName).absoluteFile
|
||||
Files.deleteIfExists(encryptedFile.toPath())
|
||||
|
||||
// WITH BASE64 By default the encoded file has a line break every 64 characters
|
||||
// encryptedFile.writeBytes(Base64.getMimeEncoder(64, "\n".toByteArray(Charsets.US_ASCII)).encode(finalEncrypted))
|
||||
|
||||
// No base64
|
||||
encryptedFile.writeBytes(finalEncrypted)
|
||||
}
|
||||
}
|
||||
catch (e: BadPaddingException) {
|
||||
// AKA "something went wrong"
|
||||
throw IllegalStateException("Bad password, algorithm, mode or padding;" + " no salt, wrong number of iterations or corrupted ciphertext.")
|
||||
}
|
||||
catch (e: IllegalBlockSizeException) {
|
||||
throw IllegalStateException("Bad algorithm, mode or corrupted (resized) ciphertext.")
|
||||
}
|
||||
catch (e: GeneralSecurityException) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
catch (e: IOException) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun concat(a: ByteArray, b: ByteArray): ByteArray {
|
||||
val c = ByteArray(a.size + b.size)
|
||||
System.arraycopy(a, 0, c, 0, a.size)
|
||||
System.arraycopy(b, 0, c, a.size, b.size)
|
||||
return c
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package dorkbox.util.crypto
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes/11786924#11786924
|
||||
internal object OpenSSLPBECommon {
|
||||
const val SALT_SIZE_BYTES = 8
|
||||
const val OPENSSL_HEADER_STRING = "Salted__"
|
||||
val OPENSSL_HEADER_STRING_BYTES = OPENSSL_HEADER_STRING.toByteArray(Charsets.US_ASCII)
|
||||
|
||||
private val hashDigest = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
fun toByteArray(chars: CharArray): ByteArray {
|
||||
val bytes = ByteArray(chars.size)
|
||||
|
||||
for (i in bytes.indices) {
|
||||
bytes[i] = chars[i].code.toByte()
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class,
|
||||
InvalidKeySpecException::class,
|
||||
InvalidKeyException::class,
|
||||
NoSuchPaddingException::class,
|
||||
InvalidAlgorithmParameterException::class
|
||||
)
|
||||
fun initializeCipher(password: String, salt: ByteArray, cipherMode: Int): Cipher {
|
||||
val passwordBytes = password.toByteArray(Charsets.US_ASCII)
|
||||
|
||||
hashDigest.update(passwordBytes)
|
||||
hashDigest.update(salt)
|
||||
|
||||
var hash = hashDigest.digest()
|
||||
var keyAndIV = hash.clone()
|
||||
|
||||
// 1 round
|
||||
hashDigest.update(hash)
|
||||
hashDigest.update(passwordBytes)
|
||||
hashDigest.update(salt)
|
||||
|
||||
hash = hashDigest.digest()
|
||||
keyAndIV = concat(keyAndIV, hash)
|
||||
|
||||
val keyBytes = keyAndIV.copyOfRange(0, 32)
|
||||
val ivBytes = keyAndIV.copyOfRange(32, 48)
|
||||
|
||||
val key = SecretKeySpec(keyBytes, "AES")
|
||||
val iv = IvParameterSpec(ivBytes)
|
||||
|
||||
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
|
||||
cipher.init(cipherMode, key, iv);
|
||||
|
||||
return cipher
|
||||
}
|
||||
|
||||
private
|
||||
fun concat(a: ByteArray, b: ByteArray): ByteArray {
|
||||
val c = ByteArray(a.size + b.size)
|
||||
System.arraycopy(a, 0, c, 0, a.size)
|
||||
System.arraycopy(b, 0, c, a.size, b.size)
|
||||
return c
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package dorkbox.util.crypto
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.IllegalBlockSizeException
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
import kotlin.experimental.and
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
// https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes/11786924#11786924
|
||||
class OpenSSLPBEInputStream @Throws(IOException::class)
|
||||
constructor(private val inStream: InputStream, password: String) : InputStream() {
|
||||
companion object {
|
||||
private const val READ_BLOCK_SIZE = 64 * 1024
|
||||
}
|
||||
|
||||
private val cipher: Cipher
|
||||
private val bufferCipher = ByteArray(READ_BLOCK_SIZE)
|
||||
|
||||
private var bufferClear: ByteArray? = null
|
||||
|
||||
private var index = Integer.MAX_VALUE
|
||||
private var maxIndex = 0
|
||||
|
||||
init {
|
||||
try {
|
||||
val salt = readSalt()
|
||||
cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.DECRYPT_MODE)
|
||||
}
|
||||
catch (e: InvalidKeySpecException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: NoSuchPaddingException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: NoSuchAlgorithmException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: InvalidKeyException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: InvalidAlgorithmParameterException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun readSalt(): ByteArray {
|
||||
|
||||
val headerBytes = ByteArray(OpenSSLPBECommon.OPENSSL_HEADER_STRING.length)
|
||||
inStream.read(headerBytes)
|
||||
val headerString = String(headerBytes, Charsets.US_ASCII)
|
||||
|
||||
if (OpenSSLPBECommon.OPENSSL_HEADER_STRING != headerString) {
|
||||
throw IOException("unexpected magic bytes $headerString")
|
||||
}
|
||||
|
||||
val salt = ByteArray(OpenSSLPBECommon.SALT_SIZE_BYTES)
|
||||
inStream.read(salt)
|
||||
|
||||
return salt
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun read(): Int {
|
||||
if (index > maxIndex) {
|
||||
index = 0
|
||||
val read = inStream.read(bufferCipher)
|
||||
if (read != -1) {
|
||||
bufferClear = cipher.update(bufferCipher, 0, read)
|
||||
}
|
||||
if (read == -1 || bufferClear == null || bufferClear!!.isEmpty()) {
|
||||
try {
|
||||
bufferClear = cipher.doFinal()
|
||||
}
|
||||
catch (e: IllegalBlockSizeException) {
|
||||
bufferClear = null
|
||||
}
|
||||
catch (e: BadPaddingException) {
|
||||
bufferClear = null
|
||||
}
|
||||
|
||||
}
|
||||
if (bufferClear == null || bufferClear!!.isEmpty()) {
|
||||
return -1
|
||||
}
|
||||
maxIndex = bufferClear!!.size - 1
|
||||
}
|
||||
|
||||
return (bufferClear!![index++] and 0xFF.toByte()).toInt()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun available(): Int {
|
||||
return inStream.available()
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package dorkbox.util.crypto
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.IllegalBlockSizeException
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
// https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes/11786924#11786924
|
||||
class OpenSSLPBEOutputStream @Throws(IOException::class)
|
||||
constructor(private val outStream: OutputStream, password: String) : OutputStream() {
|
||||
companion object {
|
||||
private const val BUFFER_SIZE = 5 * 1024 * 1024
|
||||
}
|
||||
|
||||
private val cipher: Cipher
|
||||
private val buffer = ByteArray(BUFFER_SIZE)
|
||||
private var bufferIndex: Int = 0
|
||||
|
||||
init {
|
||||
try {
|
||||
// Create and use a random SALT for each instance of this output stream.
|
||||
val salt = ByteArray(OpenSSLPBECommon.SALT_SIZE_BYTES)
|
||||
val secureRandom = SecureRandom()
|
||||
secureRandom.nextBytes(salt)
|
||||
cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.ENCRYPT_MODE)
|
||||
|
||||
// Write header
|
||||
outStream.write(OpenSSLPBECommon.OPENSSL_HEADER_STRING_BYTES)
|
||||
outStream.write(salt)
|
||||
}
|
||||
catch (e: InvalidKeySpecException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: NoSuchPaddingException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: NoSuchAlgorithmException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: InvalidAlgorithmParameterException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun write(b: Int) {
|
||||
buffer[bufferIndex] = b.toByte()
|
||||
bufferIndex++
|
||||
|
||||
// only update the digest and write out the buffer if it's enough (this is a slow operation)
|
||||
if (bufferIndex == BUFFER_SIZE) {
|
||||
val result = cipher.update(buffer, 0, bufferIndex)
|
||||
outStream.write(result)
|
||||
bufferIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun flush() {
|
||||
if (bufferIndex > 0) {
|
||||
val result: ByteArray
|
||||
try {
|
||||
result = cipher.doFinal(buffer, 0, bufferIndex)
|
||||
outStream.write(result)
|
||||
}
|
||||
catch (e: IllegalBlockSizeException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
catch (e: BadPaddingException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
|
||||
bufferIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
flush()
|
||||
outStream.close()
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.crypto;
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto.signers;
|
||||
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.Signer;
|
||||
import org.bouncycastle.crypto.signers.DSADigestSigner;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.jcajce.provider.util.DigestFactory;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.bc.BcContentSignerBuilder;
|
||||
|
||||
public
|
||||
class BcECDSAContentSignerBuilder extends BcContentSignerBuilder {
|
||||
|
||||
public
|
||||
BcECDSAContentSignerBuilder(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) {
|
||||
super(sigAlgId, digAlgId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
Signer createSigner(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) throws OperatorCreationException {
|
||||
Digest digest = DigestFactory.getDigest(digAlgId.getAlgorithm()
|
||||
.getId()); // SHA1, SHA512, etc
|
||||
|
||||
return new DSADigestSigner(new ECDSASigner(), digest);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto.signers;
|
||||
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.Signer;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.bouncycastle.crypto.signers.DSADigestSigner;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.crypto.util.PublicKeyFactory;
|
||||
import org.bouncycastle.jcajce.provider.util.DigestFactory;
|
||||
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.bc.BcContentVerifierProviderBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public
|
||||
class BcECDSAContentVerifierProviderBuilder extends BcContentVerifierProviderBuilder {
|
||||
|
||||
public
|
||||
BcECDSAContentVerifierProviderBuilder(DigestAlgorithmIdentifierFinder digestAlgorithmFinder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
AsymmetricKeyParameter extractKeyParameters(SubjectPublicKeyInfo publicKeyInfo) throws IOException {
|
||||
return PublicKeyFactory.createKey(publicKeyInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
Signer createSigner(AlgorithmIdentifier sigAlgId) throws OperatorCreationException {
|
||||
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
|
||||
|
||||
Digest digest = DigestFactory.getDigest(digAlgId.getAlgorithm()
|
||||
.getId()); // 1.2.23.4.5.6, etc
|
||||
return new DSADigestSigner(new ECDSASigner(), digest);
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.crypto.signers;
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -15,10 +15,16 @@
|
|||
*/
|
||||
package dorkbox.util.entropy
|
||||
|
||||
import dorkbox.util.Sys
|
||||
import dorkbox.util.exceptions.InitializationException
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
object Entropy {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
|
||||
private var provider: EntropyProvider? = null
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.properties;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import dorkbox.util.FileUtil;
|
||||
|
||||
public
|
||||
class PropertiesProvider {
|
||||
|
||||
private final Properties properties = new SortedProperties();
|
||||
private final File propertiesFile;
|
||||
private String comments = "Settings and configuration file. Strings must be escape formatted!";
|
||||
|
||||
public
|
||||
PropertiesProvider(String propertiesFile) {
|
||||
this(new File(propertiesFile));
|
||||
}
|
||||
|
||||
public
|
||||
PropertiesProvider(File propertiesFile) {
|
||||
if (propertiesFile == null) {
|
||||
throw new NullPointerException("propertiesFile");
|
||||
}
|
||||
|
||||
propertiesFile = FileUtil.INSTANCE.normalize(propertiesFile);
|
||||
// make sure the parent dir exists...
|
||||
File parentFile = propertiesFile.getParentFile();
|
||||
if (parentFile != null && !parentFile.exists()) {
|
||||
if (!parentFile.mkdirs()) {
|
||||
throw new RuntimeException("Unable to create directories for: " + propertiesFile);
|
||||
}
|
||||
}
|
||||
|
||||
this.propertiesFile = propertiesFile;
|
||||
|
||||
_load();
|
||||
}
|
||||
|
||||
public
|
||||
void setComments(String comments) {
|
||||
this.comments = comments;
|
||||
}
|
||||
|
||||
private
|
||||
void _load() {
|
||||
if (!this.propertiesFile.canRead() || !this.propertiesFile.exists()) {
|
||||
// in this case, our properties file doesn't exist yet... create one!
|
||||
_save();
|
||||
}
|
||||
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(this.propertiesFile);
|
||||
this.properties.load(fis);
|
||||
fis.close();
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// oops!
|
||||
System.err.println("Properties cannot load!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
void _save() {
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(this.propertiesFile);
|
||||
this.properties.store(fos, this.comments);
|
||||
fos.flush();
|
||||
fos.close();
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("Properties cannot save!");
|
||||
} catch (IOException e) {
|
||||
// oops!
|
||||
System.err.println("Properties cannot save!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final synchronized
|
||||
void remove(final String key) {
|
||||
this.properties.remove(key);
|
||||
_save();
|
||||
}
|
||||
|
||||
@SuppressWarnings("AutoBoxing")
|
||||
public final synchronized
|
||||
void save(final String key, Object value) {
|
||||
if (key == null || value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof Color) {
|
||||
value = ((Color) value).getRGB();
|
||||
}
|
||||
|
||||
this.properties.setProperty(key, value.toString());
|
||||
|
||||
_save();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "AutoUnboxing"})
|
||||
public synchronized
|
||||
<T> T get(String key, Class<T> clazz) {
|
||||
if (key == null || clazz == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String property = this.properties.getProperty(key);
|
||||
if (property == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// special cases
|
||||
try {
|
||||
if (clazz.equals(Integer.class)) {
|
||||
return (T) Integer.valueOf(Integer.parseInt(property));
|
||||
}
|
||||
if (clazz.equals(Long.class)) {
|
||||
return (T) Long.valueOf(Long.parseLong(property));
|
||||
}
|
||||
if (clazz.equals(Color.class)) {
|
||||
return (T) new Color(Integer.parseInt(property), true);
|
||||
}
|
||||
|
||||
else {
|
||||
return (T) property;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Properties Loader for property: " + key + System.getProperty("line.separator") + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return "PropertiesProvider [" + this.propertiesFile + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.properties
|
||||
|
||||
import java.awt.Color
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
|
||||
class PropertiesProvider(propertiesFile: File) {
|
||||
private val properties: Properties = SortedProperties()
|
||||
private val propertiesFile: File
|
||||
private var comments = "Settings and configuration file. Strings must be escape formatted!"
|
||||
|
||||
constructor(propertiesFile: String) : this(File(propertiesFile))
|
||||
|
||||
init {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val propertiesFile = propertiesFile.normalize()
|
||||
|
||||
// make sure the parent dir exists...
|
||||
val parentFile = propertiesFile.parentFile
|
||||
if (parentFile != null && !parentFile.exists()) {
|
||||
if (!parentFile.mkdirs()) {
|
||||
throw RuntimeException("Unable to create directories for: $propertiesFile")
|
||||
}
|
||||
}
|
||||
|
||||
this.propertiesFile = propertiesFile
|
||||
_load()
|
||||
}
|
||||
|
||||
fun setComments(comments: String) {
|
||||
this.comments = comments
|
||||
}
|
||||
|
||||
private fun _load() {
|
||||
if (!propertiesFile.canRead() || !propertiesFile.exists()) {
|
||||
// in this case, our properties file doesn't exist yet... create one!
|
||||
_save()
|
||||
}
|
||||
|
||||
try {
|
||||
val fis = FileInputStream(propertiesFile)
|
||||
properties.load(fis)
|
||||
fis.close()
|
||||
} catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
// oops!
|
||||
System.err.println("Properties cannot load!")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun _save() {
|
||||
try {
|
||||
val fos = FileOutputStream(propertiesFile)
|
||||
properties.store(fos, comments)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
} catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
System.err.println("Properties cannot save!")
|
||||
} catch (e: IOException) {
|
||||
// oops!
|
||||
System.err.println("Properties cannot save!")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun remove(key: String) {
|
||||
properties.remove(key)
|
||||
_save()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun save(key: String?, value: Any?) {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var value = value
|
||||
if (key == null || value == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (value is Color) {
|
||||
value = value.rgb
|
||||
}
|
||||
|
||||
properties.setProperty(key, value.toString())
|
||||
_save()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Synchronized
|
||||
operator fun <T> get(key: String?, clazz: Class<T>?): T? {
|
||||
if (key == null || clazz == null) {
|
||||
return null
|
||||
}
|
||||
val property = properties.getProperty(key) ?: return null
|
||||
|
||||
// special cases
|
||||
return try {
|
||||
if (clazz == Int::class.java) {
|
||||
return Integer.valueOf(property.toInt()) as T
|
||||
}
|
||||
if (clazz == Long::class.java) {
|
||||
return java.lang.Long.valueOf(property.toLong()) as T
|
||||
}
|
||||
if (clazz == Color::class.java) {
|
||||
Color(property.toInt(), true) as T
|
||||
} else {
|
||||
property as T
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Properties Loader for property: " + key + System.getProperty("line.separator") + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "PropertiesProvider [" + propertiesFile + "]"
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.properties;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public
|
||||
class SortedProperties extends Properties {
|
||||
|
||||
private static final long serialVersionUID = 3988064683926999433L;
|
||||
|
||||
private final Comparator<Object> compare = new Comparator<Object>() {
|
||||
@Override
|
||||
public
|
||||
int compare(Object o1, Object o2) {
|
||||
return o1.toString()
|
||||
.compareTo(o2.toString());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
Enumeration<Object> keys() {
|
||||
Enumeration<Object> keysEnum = super.keys();
|
||||
|
||||
Vector<Object> vector = new Vector<Object>(size());
|
||||
for (; keysEnum.hasMoreElements(); ) {
|
||||
vector.add(keysEnum.nextElement());
|
||||
}
|
||||
|
||||
Collections.sort(vector, this.compare);
|
||||
|
||||
return vector.elements();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.properties
|
||||
|
||||
import dorkbox.util.Sys
|
||||
import java.util.*
|
||||
|
||||
class SortedProperties : Properties() {
|
||||
private val compare = Comparator<Any> { o1, o2 ->
|
||||
o1.toString().compareTo(o2.toString())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun keys(): Enumeration<Any> {
|
||||
val keysEnum = super.keys()
|
||||
val vector: Vector<Any> = Vector<Any>(this.size)
|
||||
|
||||
while (keysEnum.hasMoreElements()) {
|
||||
vector.add(keysEnum.nextElement())
|
||||
}
|
||||
|
||||
Collections.sort(vector, compare)
|
||||
return vector.elements()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = 3988064683926999433L
|
||||
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@
|
|||
|
||||
package dorkbox.util.sync
|
||||
|
||||
import dorkbox.util.Sys
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.withTimeout
|
||||
|
@ -41,6 +42,13 @@ import java.util.concurrent.*
|
|||
* @param initialCount this is the initial count specified when the latch was created
|
||||
*/
|
||||
open class AbstractLatch(val initialCount: Int, val trigger: Trigger) : Deferred<Unit> by trigger {
|
||||
companion object {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
val version = Sys.version
|
||||
}
|
||||
|
||||
/**
|
||||
* The current latch count affected by the count*() methods.
|
||||
*
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.util.classes;
|
||||
|
||||
/**
|
||||
* Required for intellij to not complain regarding `module-info` for a multi-release jar.
|
||||
* This file is completely ignored by the gradle build process
|
||||
*/
|
||||
public
|
||||
class EmptyClass {}
|
|
@ -2,9 +2,6 @@ module dorkbox.utilities {
|
|||
exports dorkbox.exit;
|
||||
exports dorkbox.urlHandler;
|
||||
exports dorkbox.util;
|
||||
exports dorkbox.util.classes;
|
||||
exports dorkbox.util.crypto;
|
||||
exports dorkbox.util.crypto.signers;
|
||||
exports dorkbox.util.entropy;
|
||||
exports dorkbox.util.exceptions;
|
||||
exports dorkbox.util.gwt;
|
||||
|
@ -12,7 +9,6 @@ module dorkbox.utilities {
|
|||
exports dorkbox.util.sync;
|
||||
exports dorkbox.util.userManagement;
|
||||
|
||||
requires transitive dorkbox.executor;
|
||||
requires transitive dorkbox.updates;
|
||||
requires transitive dorkbox.os;
|
||||
|
||||
|
|
|
@ -1,277 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class MersenneTwisterFastTest {
|
||||
|
||||
@Test
|
||||
public void mersenneTwisterTest() throws IOException {
|
||||
int j;
|
||||
|
||||
MersenneTwisterFast r;
|
||||
|
||||
// CORRECTNESS TEST
|
||||
// COMPARE WITH
|
||||
// http://www.math.keio.ac.jp/matumoto/CODES/MT2002/mt19937ar.out
|
||||
|
||||
r = new MersenneTwisterFast(new int[] {0x123,0x234,0x345,0x456});
|
||||
// System.out.println("Output of MersenneTwisterFast with new (2002/1/26) seeding mechanism");
|
||||
for (j = 0; j < 1000; j++) {
|
||||
// first, convert the int from signed to "unsigned"
|
||||
long l = r.nextInt();
|
||||
if (l < 0) {
|
||||
l += 4294967296L; // max int value
|
||||
}
|
||||
String s = String.valueOf(l);
|
||||
while (s.length() < 10) {
|
||||
s = " " + s; // buffer
|
||||
}
|
||||
System.out.print(s + " ");
|
||||
if (j % 5 == 4) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
// SPEED TEST
|
||||
|
||||
final long SEED = 4357;
|
||||
|
||||
int xx;
|
||||
long ms;
|
||||
System.out.println("\nTime to test grabbing 100000000 ints");
|
||||
|
||||
Random rr = new Random(SEED);
|
||||
xx = 0;
|
||||
ms = System.currentTimeMillis();
|
||||
for (j = 0; j < 100000000; j++) {
|
||||
xx += rr.nextInt();
|
||||
}
|
||||
System.out.println("java.util.Random: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx);
|
||||
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
ms = System.currentTimeMillis();
|
||||
xx = 0;
|
||||
for (j = 0; j < 100000000; j++) {
|
||||
xx += r.nextInt();
|
||||
}
|
||||
System.out.println("Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: "
|
||||
+ xx);
|
||||
|
||||
// TEST TO COMPARE TYPE CONVERSION BETWEEN
|
||||
// MersenneTwisterFast.java AND MersenneTwister.java
|
||||
|
||||
boolean test = false;
|
||||
System.out.println("\nGrab the first 1000 booleans");
|
||||
ms = System.currentTimeMillis();
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
// System.out.print(r.nextBoolean() + " ");
|
||||
test = r.nextBoolean();
|
||||
if (j % 8 == 7) {
|
||||
// System.out.println();
|
||||
test = false;
|
||||
}
|
||||
}
|
||||
if (!(j % 8 == 7)) {
|
||||
// System.out.println();
|
||||
test = true;
|
||||
}
|
||||
|
||||
System.out.println("Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: "
|
||||
+ xx + "" + test);
|
||||
|
||||
System.out.println("\nGrab 1000 booleans of increasing probability using nextBoolean(double)");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
ms = System.currentTimeMillis();
|
||||
for (j = 0; j < 1000; j++) {
|
||||
// System.out.print(r.nextBoolean(j / 999.0) + " ");
|
||||
test = r.nextBoolean(j / 999.0);
|
||||
if (j % 8 == 7) {
|
||||
// System.out.println();
|
||||
test = false;
|
||||
}
|
||||
}
|
||||
if (!(j % 8 == 7)) {
|
||||
// System.out.println();
|
||||
test = true;
|
||||
}
|
||||
|
||||
System.out.println("Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: "
|
||||
+ xx + "" + test);
|
||||
|
||||
System.out.println("\nGrab 1000 booleans of increasing probability using nextBoolean(float)");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
ms = System.currentTimeMillis();
|
||||
for (j = 0; j < 1000; j++) {
|
||||
// System.out.print(r.nextBoolean(j / 999.0f) + " ");
|
||||
test = r.nextBoolean(j / 999.0f);
|
||||
if (j % 8 == 7) {
|
||||
test = false;
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 8 == 7)) {
|
||||
// System.out.println();
|
||||
test = true;
|
||||
}
|
||||
|
||||
System.out.println("Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: "
|
||||
+ xx + "" + test);
|
||||
|
||||
|
||||
byte[] bytes = new byte[1000];
|
||||
System.out.println("\nGrab the first 1000 bytes using nextBytes");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
r.nextBytes(bytes);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(bytes[j] + " ");
|
||||
if (j % 16 == 15) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 16 == 15)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
byte b;
|
||||
System.out.println("\nGrab the first 1000 bytes -- must be same as nextBytes");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print((b = r.nextByte()) + " ");
|
||||
if (b != bytes[j]) {
|
||||
System.out.print("BAD ");
|
||||
}
|
||||
if (j % 16 == 15) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 16 == 15)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 shorts");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextShort() + " ");
|
||||
if (j % 8 == 7) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 8 == 7)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 ints");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextInt() + " ");
|
||||
if (j % 4 == 3) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 4 == 3)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 ints of different sizes");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
int max = 1;
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextInt(max) + " ");
|
||||
max *= 2;
|
||||
if (max <= 0) {
|
||||
max = 1;
|
||||
}
|
||||
if (j % 4 == 3) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 4 == 3)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 longs");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextLong() + " ");
|
||||
if (j % 3 == 2) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 3 == 2)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 longs of different sizes");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
long max2 = 1;
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextLong(max2) + " ");
|
||||
max2 *= 2;
|
||||
if (max2 <= 0) {
|
||||
max2 = 1;
|
||||
}
|
||||
if (j % 4 == 3) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 4 == 3)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 floats");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextFloat() + " ");
|
||||
if (j % 4 == 3) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 4 == 3)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 doubles");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextDouble() + " ");
|
||||
if (j % 3 == 2) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 3 == 2)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("\nGrab the first 1000 gaussian doubles");
|
||||
r = new MersenneTwisterFast(SEED);
|
||||
for (j = 0; j < 1000; j++) {
|
||||
System.out.print(r.nextGaussian() + " ");
|
||||
if (j % 3 == 2) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
if (!(j % 3 == 2)) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class MersenneTwisterFastTest {
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun mersenneTwisterTest() {
|
||||
var j: Int
|
||||
var r: MersenneTwisterFast
|
||||
|
||||
// CORRECTNESS TEST
|
||||
// COMPARE WITH
|
||||
// http://www.math.keio.ac.jp/matumoto/CODES/MT2002/mt19937ar.out
|
||||
r = MersenneTwisterFast(intArrayOf(0x123, 0x234, 0x345, 0x456))
|
||||
// System.out.println("Output of MersenneTwisterFast with new (2002/1/26) seeding mechanism");
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
|
||||
// first, convert the int from signed to "unsigned"
|
||||
var l = r.nextInt().toLong()
|
||||
if (l < 0) {
|
||||
l += 4294967296L // max int value
|
||||
}
|
||||
var s = l.toString()
|
||||
while (s.length < 10) {
|
||||
s = " $s" // buffer
|
||||
}
|
||||
print("$s ")
|
||||
if (j % 5 == 4) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
// SPEED TEST
|
||||
val SEED: Long = 4357
|
||||
var xx: Int
|
||||
var ms: Long
|
||||
println("\nTime to test grabbing 100000000 ints")
|
||||
val rr = Random(SEED)
|
||||
xx = 0
|
||||
ms = System.currentTimeMillis()
|
||||
j = 0
|
||||
while (j < 100000000) {
|
||||
xx += rr.nextInt()
|
||||
j++
|
||||
}
|
||||
println("java.util.Random: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx)
|
||||
r = MersenneTwisterFast(SEED)
|
||||
ms = System.currentTimeMillis()
|
||||
xx = 0
|
||||
j = 0
|
||||
while (j < 100000000) {
|
||||
xx += r.nextInt()
|
||||
j++
|
||||
}
|
||||
println(
|
||||
"Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx
|
||||
)
|
||||
|
||||
// TEST TO COMPARE TYPE CONVERSION BETWEEN
|
||||
// MersenneTwisterFast.java AND MersenneTwister.java
|
||||
var test = false
|
||||
println("\nGrab the first 1000 booleans")
|
||||
ms = System.currentTimeMillis()
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
|
||||
// System.out.print(r.nextBoolean() + " ");
|
||||
test = r.nextBoolean()
|
||||
if (j % 8 == 7) {
|
||||
// System.out.println();
|
||||
test = false
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 8 != 7) {
|
||||
// System.out.println();
|
||||
test = true
|
||||
}
|
||||
println(
|
||||
"Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx + "" + test
|
||||
)
|
||||
println("\nGrab 1000 booleans of increasing probability using nextBoolean(double)")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
ms = System.currentTimeMillis()
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
|
||||
// System.out.print(r.nextBoolean(j / 999.0) + " ");
|
||||
test = r.nextBoolean(j / 999.0)
|
||||
if (j % 8 == 7) {
|
||||
// System.out.println();
|
||||
test = false
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 8 != 7) {
|
||||
// System.out.println();
|
||||
test = true
|
||||
}
|
||||
println(
|
||||
"Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx + "" + test
|
||||
)
|
||||
println("\nGrab 1000 booleans of increasing probability using nextBoolean(float)")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
ms = System.currentTimeMillis()
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
|
||||
// System.out.print(r.nextBoolean(j / 999.0f) + " ");
|
||||
test = r.nextBoolean(j / 999.0f)
|
||||
if (j % 8 == 7) {
|
||||
test = false
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 8 != 7) {
|
||||
// System.out.println();
|
||||
test = true
|
||||
}
|
||||
println(
|
||||
"Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx + "" + test
|
||||
)
|
||||
val bytes = ByteArray(1000)
|
||||
println("\nGrab the first 1000 bytes using nextBytes")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
r.nextBytes(bytes)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(bytes[j].toString() + " ")
|
||||
if (j % 16 == 15) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 16 != 15) {
|
||||
println()
|
||||
}
|
||||
var b: Byte
|
||||
println("\nGrab the first 1000 bytes -- must be same as nextBytes")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextByte().also { b = it }.toString() + " ")
|
||||
if (b != bytes[j]) {
|
||||
print("BAD ")
|
||||
}
|
||||
if (j % 16 == 15) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 16 != 15) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 shorts")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextShort().toString() + " ")
|
||||
if (j % 8 == 7) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 8 != 7) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 ints")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextInt().toString() + " ")
|
||||
if (j % 4 == 3) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 4 != 3) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 ints of different sizes")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
var max = 1
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextInt(max).toString() + " ")
|
||||
max *= 2
|
||||
if (max <= 0) {
|
||||
max = 1
|
||||
}
|
||||
if (j % 4 == 3) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 4 != 3) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 longs")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextLong().toString() + " ")
|
||||
if (j % 3 == 2) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 3 != 2) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 longs of different sizes")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
var max2: Long = 1
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextLong(max2).toString() + " ")
|
||||
max2 *= 2
|
||||
if (max2 <= 0) {
|
||||
max2 = 1
|
||||
}
|
||||
if (j % 4 == 3) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 4 != 3) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 floats")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextFloat().toString() + " ")
|
||||
if (j % 4 == 3) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 4 != 3) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 doubles")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextDouble().toString() + " ")
|
||||
if (j % 3 == 2) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 3 != 2) {
|
||||
println()
|
||||
}
|
||||
println("\nGrab the first 1000 gaussian doubles")
|
||||
r = MersenneTwisterFast(SEED)
|
||||
j = 0
|
||||
while (j < 1000) {
|
||||
print(r.nextGaussian().toString() + " ")
|
||||
if (j % 3 == 2) {
|
||||
println()
|
||||
}
|
||||
j++
|
||||
}
|
||||
if (j % 3 != 2) {
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.*
|
||||
|
||||
class TimeTest {
|
||||
@Test
|
||||
fun time() {
|
||||
TimeUnit.DAYS.toNanos(3).also {
|
||||
Assert.assertEquals("3 days", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.DAYS.toNanos(30).also {
|
||||
Assert.assertEquals("30 days", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.DAYS.toNanos(300).also {
|
||||
Assert.assertEquals("300 days", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.DAYS.toNanos(3000).also {
|
||||
Assert.assertEquals("3000 days", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
|
||||
|
||||
TimeUnit.HOURS.toNanos(3).also {
|
||||
Assert.assertEquals("3 hours", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.MINUTES.toNanos(3).also {
|
||||
Assert.assertEquals("3 minutes", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.SECONDS.toNanos(3).also {
|
||||
Assert.assertEquals("3 seconds", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.MILLISECONDS.toNanos(3).also {
|
||||
Assert.assertEquals("3 milli-seconds", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.MICROSECONDS.toNanos(3).also {
|
||||
Assert.assertEquals("3 micro-seconds", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
TimeUnit.NANOSECONDS.toNanos(3).also {
|
||||
Assert.assertEquals("3 nano-seconds", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
|
||||
|
||||
TimeUnit.NANOSECONDS.toNanos(1).also {
|
||||
Assert.assertEquals("1 nano-second", Sys.getTimePrettyFull(it))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,343 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AesTest {
|
||||
|
||||
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
|
||||
private static String entropySeed = "asdjhasdkljalksdfhlaks4356268909087s0dfgkjh255124515hasdg87";
|
||||
|
||||
@Test
|
||||
public void AesGcm() throws IOException {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key (32 bytes)
|
||||
rand.nextBytes(iv); // 128bit block size (16 bytes)
|
||||
|
||||
|
||||
byte[] encryptAES = CryptoAES.encrypt(aesEngine, key, iv, bytes, logger);
|
||||
byte[] decryptAES = CryptoAES.decrypt(aesEngine, key, iv, encryptAES, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptAES)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptAES)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void AesBlock() throws IOException {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 16bit block size
|
||||
|
||||
|
||||
byte[] encryptAES = CryptoAES.encrypt(aesEngine, key, iv, bytes, logger);
|
||||
byte[] decryptAES = CryptoAES.decrypt(aesEngine, key, iv, encryptAES, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptAES)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptAES)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void AesGcmStream() throws IOException {
|
||||
byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 128bit block size
|
||||
|
||||
|
||||
boolean success = CryptoAES.encryptStream(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] encryptBytes = outputStream.toByteArray();
|
||||
|
||||
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
success = CryptoAES.decryptStream(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] decryptBytes = outputStream.toByteArray();
|
||||
|
||||
if (Arrays.equals(originalBytes, encryptBytes)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(originalBytes, decryptBytes)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void AesBlockStream() throws IOException {
|
||||
byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 128bit block size
|
||||
|
||||
|
||||
boolean success = CryptoAES.encryptStream(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] encryptBytes = outputStream.toByteArray();
|
||||
|
||||
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
success = CryptoAES.decryptStream(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] decryptBytes = outputStream.toByteArray();
|
||||
|
||||
if (Arrays.equals(originalBytes, encryptBytes)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(originalBytes, decryptBytes)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void AesWithIVGcm() throws IOException {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
|
||||
byte[] key = new byte[32]; // 256bit key
|
||||
byte[] iv = new byte[aesEngine.getUnderlyingCipher().getBlockSize()];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key);
|
||||
rand.nextBytes(iv);
|
||||
|
||||
|
||||
byte[] encryptAES = CryptoAES.encryptWithIV(aesEngine, key, iv, bytes, logger);
|
||||
byte[] decryptAES = CryptoAES.decryptWithIV(aesEngine, key, encryptAES, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptAES)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptAES)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void AesWithIVBlock() throws IOException {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
|
||||
byte[] key = new byte[32]; // 256bit key
|
||||
byte[] iv = new byte[aesEngine.getUnderlyingCipher().getBlockSize()];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key);
|
||||
rand.nextBytes(iv);
|
||||
|
||||
|
||||
byte[] encryptAES = CryptoAES.encryptWithIV(aesEngine, key, iv, bytes, logger);
|
||||
byte[] decryptAES = CryptoAES.decryptWithIV(aesEngine, key, encryptAES, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptAES)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptAES)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void AesWithIVGcmStream() throws IOException {
|
||||
byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 128bit block size
|
||||
|
||||
|
||||
boolean success = CryptoAES.encryptStreamWithIV(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] encryptBytes = outputStream.toByteArray();
|
||||
|
||||
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
success = CryptoAES.decryptStreamWithIV(aesEngine, key, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] decryptBytes = outputStream.toByteArray();
|
||||
|
||||
if (Arrays.equals(originalBytes, encryptBytes)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(originalBytes, decryptBytes)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void AesWithIVBlockStream() throws IOException {
|
||||
byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
SecureRandom rand = new SecureRandom(entropySeed.getBytes());
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
// note: the IV needs to be VERY unique!
|
||||
rand.nextBytes(key); // 256bit key
|
||||
rand.nextBytes(iv); // 128bit block size
|
||||
|
||||
|
||||
boolean success = CryptoAES.encryptStreamWithIV(aesEngine, key, iv, inputStream, outputStream, logger);
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] encryptBytes = outputStream.toByteArray();
|
||||
|
||||
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
success = CryptoAES.decryptStreamWithIV(aesEngine, key, inputStream, outputStream, logger);
|
||||
|
||||
|
||||
if (!success) {
|
||||
fail("crypto was not successful");
|
||||
}
|
||||
|
||||
byte[] decryptBytes = outputStream.toByteArray();
|
||||
|
||||
if (Arrays.equals(originalBytes, encryptBytes)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(originalBytes, decryptBytes)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x509.DSAParameter;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.params.DSAParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
||||
import org.bouncycastle.crypto.util.PublicKeyFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class DsaTest {
|
||||
private static String entropySeed = "asdjhaffasttjjhgpx600gn,-356268909087s0dfgkjh255124515hasdg87";
|
||||
|
||||
// Note: this is here just for keeping track of how this is done. This should NOT be used, and instead ECC crypto used instead.
|
||||
@Test
|
||||
public void Dsa() {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoDSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
DSAPrivateKeyParameters privateKey = (DSAPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
DSAPublicKeyParameters publicKey = (DSAPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
BigInteger[] signature = CryptoDSA.generateSignature(privateKey, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verify1 = CryptoDSA.verifySignature(publicKey, bytes, signature);
|
||||
|
||||
if (!verify1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
byte[] bytes2 = "hello, my name is inigo montoya FAILED VERSION".getBytes();
|
||||
|
||||
if (Arrays.equals(bytes, bytes2)) {
|
||||
fail("failed to create different byte arrays for testing bad messages");
|
||||
}
|
||||
|
||||
|
||||
|
||||
boolean verify2 = CryptoDSA.verifySignature(publicKey, bytes2, signature);
|
||||
|
||||
if (verify2) {
|
||||
fail("failed signature verification with bad message");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void DsaJceSerializaion() throws IOException {
|
||||
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoDSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
DSAPrivateKeyParameters privateKey = (DSAPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
DSAPublicKeyParameters publicKey = (DSAPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
// public key as bytes.
|
||||
DSAParameters parameters = publicKey.getParameters();
|
||||
byte[] bs = new SubjectPublicKeyInfo(
|
||||
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa,
|
||||
new DSAParameter(parameters.getP(), parameters.getQ(), parameters.getG()).toASN1Primitive()),
|
||||
new ASN1Integer(publicKey.getY())).getEncoded();
|
||||
|
||||
|
||||
|
||||
parameters = privateKey.getParameters();
|
||||
byte[] bs2 = new PrivateKeyInfo(
|
||||
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa,
|
||||
new DSAParameter(parameters.getP(), parameters.getQ(), parameters.getG()).toASN1Primitive()),
|
||||
new ASN1Integer(privateKey.getX())).getEncoded();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
DSAPrivateKeyParameters privateKey2 = (DSAPrivateKeyParameters) PrivateKeyFactory.createKey(bs2);
|
||||
DSAPublicKeyParameters publicKey2 = (DSAPublicKeyParameters) PublicKeyFactory.createKey(bs);
|
||||
|
||||
|
||||
|
||||
// test via signing
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
|
||||
BigInteger[] signature = CryptoDSA.generateSignature(privateKey, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verify1 = CryptoDSA.verifySignature(publicKey, bytes, signature);
|
||||
|
||||
if (!verify1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
boolean verify2 = CryptoDSA.verifySignature(publicKey2, bytes, signature);
|
||||
|
||||
if (!verify2) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// now reverse who signs what.
|
||||
BigInteger[] signatureB = CryptoDSA.generateSignature(privateKey2, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verifyB1 = CryptoDSA.verifySignature(publicKey, bytes, signatureB);
|
||||
|
||||
if (!verifyB1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
boolean verifyB2 = CryptoDSA.verifySignature(publicKey2, bytes, signatureB);
|
||||
|
||||
if (!verifyB2) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,332 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.BasicAgreement;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.engines.IESEngine;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.IESParameters;
|
||||
import org.bouncycastle.crypto.params.IESWithCipherParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
||||
import org.bouncycastle.crypto.util.PublicKeyFactory;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
import dorkbox.serializers.bouncycastle.EccPrivateKeySerializer;
|
||||
import dorkbox.serializers.bouncycastle.EccPublicKeySerializer;
|
||||
import dorkbox.serializers.bouncycastle.IesParametersSerializer;
|
||||
import dorkbox.serializers.bouncycastle.IesWithCipherParametersSerializer;
|
||||
|
||||
public class EccTest {
|
||||
|
||||
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
|
||||
private static String entropySeed = "asdjhaffasttjasdasdgfgaerym0698768.,./8909087s0dfgkjgb49bmngrSGDSG#";
|
||||
|
||||
@Test
|
||||
public void EccStreamMode() throws IOException {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
AsymmetricCipherKeyPair key2 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
IESParameters cipherParams = CryptoECC.generateSharedParameters(secureRandom);
|
||||
|
||||
IESEngine encrypt = CryptoECC.createEngine();
|
||||
IESEngine decrypt = CryptoECC.createEngine();
|
||||
|
||||
|
||||
// note: we want an ecc key that is AT LEAST 512 bits! (which is equal to AES 256)
|
||||
// using 521 bits from curve.
|
||||
CipherParameters private1 = key1.getPrivate();
|
||||
CipherParameters public1 = key1.getPublic();
|
||||
|
||||
CipherParameters private2 = key2.getPrivate();
|
||||
CipherParameters public2 = key2.getPublic();
|
||||
|
||||
byte[] message = Hex.decode(
|
||||
"123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef");
|
||||
|
||||
|
||||
// test stream mode
|
||||
byte[] encrypted = CryptoECC.encrypt(encrypt, private1, public2, cipherParams, message, logger);
|
||||
byte[] plaintext = CryptoECC.decrypt(decrypt, private2, public1, cipherParams, encrypted, logger);
|
||||
|
||||
if (Arrays.equals(encrypted, message)) {
|
||||
fail("stream cipher test failed");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(plaintext, message)) {
|
||||
fail("stream cipher test failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void EccAesMode() throws IOException {
|
||||
// test AES encrypt mode
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
AsymmetricCipherKeyPair key2 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
|
||||
PaddedBufferedBlockCipher aesEngine1 = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
||||
PaddedBufferedBlockCipher aesEngine2 = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
||||
|
||||
IESWithCipherParameters cipherParams = CryptoECC.generateSharedParametersWithCipher(secureRandom);
|
||||
|
||||
|
||||
IESEngine encrypt = CryptoECC.createEngine(aesEngine1);
|
||||
IESEngine decrypt = CryptoECC.createEngine(aesEngine2);
|
||||
|
||||
|
||||
// note: we want an ecc key that is AT LEAST 512 bits! (which is equal to AES 256)
|
||||
// using 521 bits from curve.
|
||||
CipherParameters private1 = key1.getPrivate();
|
||||
CipherParameters public1 = key1.getPublic();
|
||||
|
||||
CipherParameters private2 = key2.getPrivate();
|
||||
CipherParameters public2 = key2.getPublic();
|
||||
|
||||
byte[] message = Hex.decode("123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef");
|
||||
|
||||
// test stream mode
|
||||
byte[] encrypted = CryptoECC.encrypt(encrypt, private1, public2, cipherParams, message, logger);
|
||||
byte[] plaintext = CryptoECC.decrypt(decrypt, private2, public1, cipherParams, encrypted, logger);
|
||||
|
||||
if (Arrays.equals(encrypted, message)) {
|
||||
fail("stream cipher test failed");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(plaintext, message)) {
|
||||
fail("stream cipher test failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void Ecdh() throws IOException {
|
||||
// test DH key exchange
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
AsymmetricCipherKeyPair key2 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
BasicAgreement e1 = new ECDHCBasicAgreement();
|
||||
BasicAgreement e2 = new ECDHCBasicAgreement();
|
||||
|
||||
e1.init(key1.getPrivate());
|
||||
e2.init(key2.getPrivate());
|
||||
|
||||
BigInteger k1 = e1.calculateAgreement(key2.getPublic());
|
||||
BigInteger k2 = e2.calculateAgreement(key1.getPublic());
|
||||
|
||||
if (!k1.equals(k2)) {
|
||||
fail("ECDHC cipher test failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void EccDsa() throws IOException {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
ParametersWithRandom param = new ParametersWithRandom(key1.getPrivate(), new SecureRandom());
|
||||
|
||||
ECDSASigner ecdsa = new ECDSASigner();
|
||||
|
||||
ecdsa.init(true, param);
|
||||
|
||||
byte[] message = new BigInteger("345234598734987394672039478602934578").toByteArray();
|
||||
BigInteger[] sig = ecdsa.generateSignature(message);
|
||||
|
||||
|
||||
ecdsa.init(false, key1.getPublic());
|
||||
|
||||
if (!ecdsa.verifySignature(message, sig[0], sig[1])) {
|
||||
fail("ECDSA signature fails");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void EccSerialization() {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
AsymmetricCipherKeyPair key1 = CryptoECC.generateKeyPair(CryptoECC.default_curve, secureRandom);
|
||||
|
||||
IESParameters cipherAParams = CryptoECC.generateSharedParameters(secureRandom);
|
||||
IESWithCipherParameters cipherBParams = CryptoECC.generateSharedParametersWithCipher(secureRandom);
|
||||
|
||||
|
||||
// note: we want an ecc key that is AT LEAST 512 bits! (which is equal to AES 256)
|
||||
// using 521 bits from curve.
|
||||
ECPrivateKeyParameters private1 = (ECPrivateKeyParameters) key1.getPrivate();
|
||||
ECPublicKeyParameters public1 = (ECPublicKeyParameters) key1.getPublic();
|
||||
|
||||
|
||||
Kryo kryo = new Kryo();
|
||||
kryo.register(IESParameters.class, new IesParametersSerializer());
|
||||
kryo.register(IESWithCipherParameters.class, new IesWithCipherParametersSerializer());
|
||||
kryo.register(ECPublicKeyParameters.class, new EccPublicKeySerializer());
|
||||
kryo.register(ECPrivateKeyParameters.class, new EccPrivateKeySerializer());
|
||||
|
||||
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
Output output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, cipherAParams);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
Input input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
IESParameters cipherAParams2 = (IESParameters) kryo.readClassAndObject(input);
|
||||
|
||||
|
||||
if (!CryptoECC.compare(cipherAParams, cipherAParams2)) {
|
||||
fail("cipher parameters not equal");
|
||||
}
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
outStream = new ByteArrayOutputStream();
|
||||
output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, cipherBParams);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
IESWithCipherParameters cipherBParams2 = (IESWithCipherParameters) kryo.readClassAndObject(input);
|
||||
|
||||
if (!CryptoECC.compare(cipherBParams, cipherBParams2)) {
|
||||
fail("cipher parameters not equal");
|
||||
}
|
||||
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
outStream = new ByteArrayOutputStream();
|
||||
output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, private1);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
ECPrivateKeyParameters private2 = (ECPrivateKeyParameters) kryo.readClassAndObject(input);
|
||||
|
||||
if (!CryptoECC.compare(private1, private2)) {
|
||||
fail("private keys not equal");
|
||||
}
|
||||
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
outStream = new ByteArrayOutputStream();
|
||||
output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, public1);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
ECPublicKeyParameters public2 = (ECPublicKeyParameters) kryo.readClassAndObject(input);
|
||||
|
||||
if (!CryptoECC.compare(public1, public2)) {
|
||||
fail("public keys not equal");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void EccJceSerialization() throws IOException {
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoECC.generateKeyPair(CryptoECC.default_curve, new SecureRandom());
|
||||
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
BCECPublicKey bcecPublicKey = new BCECPublicKey("EC", publicKey, (ECParameterSpec) null, BouncyCastleProvider.CONFIGURATION);
|
||||
byte[] publicBytes = bcecPublicKey.getEncoded();
|
||||
|
||||
|
||||
|
||||
// relies on the BC public key.
|
||||
BCECPrivateKey bcecPrivateKey = new BCECPrivateKey("EC", privateKey, bcecPublicKey, (ECParameterSpec) null, BouncyCastleProvider.CONFIGURATION);
|
||||
byte[] privateBytes = bcecPrivateKey.getEncoded();
|
||||
|
||||
|
||||
|
||||
ECPublicKeyParameters publicKey2 = (ECPublicKeyParameters) PublicKeyFactory.createKey(publicBytes);
|
||||
ECPrivateKeyParameters privateKey2 = (ECPrivateKeyParameters) PrivateKeyFactory.createKey(privateBytes);
|
||||
|
||||
|
||||
|
||||
// test via signing
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
|
||||
BigInteger[] signature = CryptoECC.generateSignature("SHA384", privateKey, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verify1 = CryptoECC.verifySignature("SHA384", publicKey, bytes, signature);
|
||||
|
||||
if (!verify1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
boolean verify2 = CryptoECC.verifySignature("SHA384", publicKey2, bytes, signature);
|
||||
|
||||
if (!verify2) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// now reverse who signs what.
|
||||
BigInteger[] signatureB = CryptoECC.generateSignature("SHA384", privateKey2, new SecureRandom(entropySeed.getBytes()), bytes);
|
||||
|
||||
boolean verifyB1 = CryptoECC.verifySignature("SHA384", publicKey, bytes, signatureB);
|
||||
|
||||
if (!verifyB1) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
|
||||
boolean verifyB2 = CryptoECC.verifySignature("SHA384", publicKey2, bytes, signatureB);
|
||||
|
||||
if (!verifyB2) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
|
||||
// See: https://stackoverflow.com/questions/25992131/slow-aes-gcm-encryption-and-decryption-with-java-8u20
|
||||
// java8 performance is 3 MB/s. BC is ~43 MB/s
|
||||
public
|
||||
class PerformanceTest {
|
||||
private static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(PerformanceTest.class);
|
||||
private static String entropySeed = "asdjhasdkljalksdfhlaks4356268909087s0dfgkjh255124515hasdg87";
|
||||
|
||||
public static
|
||||
void main(String[] args) throws Exception {
|
||||
final int max = 5;
|
||||
for (int i = 0; i < max; i++) {
|
||||
System.out.println("Warming up " + (i+1) + " of " + max);
|
||||
new PerformanceTest(true);
|
||||
}
|
||||
new PerformanceTest(false);
|
||||
}
|
||||
|
||||
PerformanceTest(boolean isWarmup) {
|
||||
final byte[] bytes = new byte[64 * 1024];
|
||||
byte[] encrypted = null;
|
||||
final byte[] aesKey = new byte[32];
|
||||
final byte[] aesIV = new byte[12];
|
||||
|
||||
final Random random = new SecureRandom(entropySeed.getBytes());
|
||||
random.nextBytes(bytes);
|
||||
random.nextBytes(aesKey);
|
||||
random.nextBytes(aesIV);
|
||||
|
||||
int length = bytes.length;
|
||||
|
||||
if (!isWarmup) {
|
||||
System.out.println("Benchmarking AES-256 GCM encryption");
|
||||
}
|
||||
|
||||
long javaEncryptInputBytes = 0;
|
||||
long javaEncryptStartTime = System.currentTimeMillis();
|
||||
|
||||
// convert to bouncycastle
|
||||
GCMBlockCipher aesEngine = new GCMBlockCipher(new AESEngine());
|
||||
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||
|
||||
long encryptInitTime = 0L;
|
||||
long encryptUpdate1Time = 0L;
|
||||
long encryptDoFinalTime = 0L;
|
||||
|
||||
while (System.currentTimeMillis() - javaEncryptStartTime < 10000) {
|
||||
random.nextBytes(aesIV);
|
||||
|
||||
long n1 = System.nanoTime();
|
||||
|
||||
aesEngine.reset();
|
||||
aesEngine.init(true, aesIVAndKey);
|
||||
|
||||
if (encrypted == null) {
|
||||
int minSize = aesEngine.getOutputSize(length);
|
||||
encrypted = new byte[minSize];
|
||||
}
|
||||
|
||||
long n2 = System.nanoTime();
|
||||
int actualLength = aesEngine.processBytes(bytes, 0, length, encrypted, 0);
|
||||
|
||||
long n3 = System.nanoTime();
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(encrypted, actualLength);
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to perform AES cipher.", e);
|
||||
}
|
||||
|
||||
if (encrypted.length != actualLength) {
|
||||
byte[] result = new byte[actualLength];
|
||||
System.arraycopy(encrypted, 0, result, 0, result.length);
|
||||
encrypted = result;
|
||||
}
|
||||
|
||||
long n4 = System.nanoTime();
|
||||
|
||||
javaEncryptInputBytes += actualLength;
|
||||
|
||||
encryptInitTime = n2 - n1;
|
||||
encryptUpdate1Time = n3 - n2;
|
||||
encryptDoFinalTime = n4 - n3;
|
||||
}
|
||||
|
||||
long javaEncryptEndTime = System.currentTimeMillis();
|
||||
|
||||
if (!isWarmup) {
|
||||
System.out.println("Time init (ns): " + encryptInitTime);
|
||||
System.out.println("Time update (ns): " + encryptUpdate1Time);
|
||||
System.out.println("Time do final (ns): " + encryptDoFinalTime);
|
||||
System.out.println("Java calculated at " +
|
||||
(javaEncryptInputBytes / 1024 / 1024 / ((javaEncryptEndTime - javaEncryptStartTime) / 1000)) + " MB/s");
|
||||
|
||||
System.out.println("Benchmarking AES-256 GCM decryption");
|
||||
}
|
||||
|
||||
|
||||
long javaDecryptInputBytes = 0;
|
||||
long javaDecryptStartTime = System.currentTimeMillis();
|
||||
|
||||
length = encrypted.length;
|
||||
|
||||
long decryptInitTime = 0L;
|
||||
long decryptUpdate1Time = 0L;
|
||||
long decryptDoFinalTime = 0L;
|
||||
|
||||
while (System.currentTimeMillis() - javaDecryptStartTime < 10000) {
|
||||
long n1 = System.nanoTime();
|
||||
|
||||
aesEngine.reset();
|
||||
aesEngine.init(false, aesIVAndKey);
|
||||
|
||||
long n2 = System.nanoTime();
|
||||
|
||||
int actualLength = aesEngine.processBytes(encrypted, 0, length, bytes, 0);
|
||||
|
||||
long n3 = System.nanoTime();
|
||||
|
||||
try {
|
||||
actualLength += aesEngine.doFinal(bytes, actualLength);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Unable to perform AES cipher.", e);
|
||||
}
|
||||
|
||||
|
||||
long n4 = System.nanoTime();
|
||||
|
||||
javaDecryptInputBytes += actualLength;
|
||||
|
||||
decryptInitTime += n2 - n1;
|
||||
decryptUpdate1Time += n3 - n2;
|
||||
decryptDoFinalTime += n4 - n3;
|
||||
}
|
||||
long javaDecryptEndTime = System.currentTimeMillis();
|
||||
|
||||
if (!isWarmup) {
|
||||
System.out.println("Time init (ns): " + decryptInitTime);
|
||||
System.out.println("Time update 1 (ns): " + decryptUpdate1Time);
|
||||
System.out.println("Time do final (ns): " + decryptDoFinalTime);
|
||||
System.out.println("Total bytes processed: " + javaDecryptInputBytes);
|
||||
System.out.println("Java calculated at " +
|
||||
(javaDecryptInputBytes / 1024 / 1024 / ((javaDecryptEndTime - javaDecryptStartTime) / 1000)) + " MB/s");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||
import org.bouncycastle.crypto.encodings.OAEPEncoding;
|
||||
import org.bouncycastle.crypto.engines.RSAEngine;
|
||||
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.PSSSigner;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
import dorkbox.serializers.bouncycastle.RsaPrivateKeySerializer;
|
||||
import dorkbox.serializers.bouncycastle.RsaPublicKeySerializer;
|
||||
|
||||
public class RsaTest {
|
||||
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
|
||||
private static String entropySeed = "asdjhaffasttjjhgpx600gn,-356268909087s0dfgkjh255124515hasdg87";
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void Rsa() {
|
||||
byte[] bytes = "hello, my name is inigo montoya".getBytes();
|
||||
|
||||
AsymmetricCipherKeyPair key = CryptoRSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
|
||||
RSAKeyParameters public1 = (RSAKeyParameters) key.getPublic();
|
||||
RSAPrivateCrtKeyParameters private1 = (RSAPrivateCrtKeyParameters) key.getPrivate();
|
||||
|
||||
|
||||
RSAEngine engine = new RSAEngine();
|
||||
SHA1Digest digest = new SHA1Digest();
|
||||
OAEPEncoding rsaEngine = new OAEPEncoding(engine, digest);
|
||||
|
||||
// test encrypt/decrypt
|
||||
byte[] encryptRSA = CryptoRSA.encrypt(rsaEngine, public1, bytes, logger);
|
||||
byte[] decryptRSA = CryptoRSA.decrypt(rsaEngine, private1, encryptRSA, logger);
|
||||
|
||||
if (Arrays.equals(bytes, encryptRSA)) {
|
||||
fail("bytes should not be equal");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(bytes, decryptRSA)) {
|
||||
fail("bytes not equal");
|
||||
}
|
||||
|
||||
// test signing/verification
|
||||
PSSSigner signer = new PSSSigner(engine, digest, digest.getDigestSize());
|
||||
|
||||
byte[] signatureRSA = CryptoRSA.sign(signer, private1, bytes, logger);
|
||||
boolean verify = CryptoRSA.verify(signer, public1, signatureRSA, bytes);
|
||||
|
||||
if (!verify) {
|
||||
fail("failed signature verification");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void RsaSerialization () throws IOException {
|
||||
RSAKeyPairGenerator keyGen = new RSAKeyPairGenerator();
|
||||
RSAKeyGenerationParameters params = new RSAKeyGenerationParameters(new BigInteger("65537"), // public exponent
|
||||
new SecureRandom(entropySeed.getBytes()), //pnrg
|
||||
1024, // key length
|
||||
8); //the number of iterations of the Miller-Rabin primality test.
|
||||
keyGen.init(params);
|
||||
|
||||
|
||||
AsymmetricCipherKeyPair key = keyGen.generateKeyPair();
|
||||
|
||||
RSAKeyParameters public1 = (RSAKeyParameters) key.getPublic();
|
||||
RSAPrivateCrtKeyParameters private1 = (RSAPrivateCrtKeyParameters) key.getPrivate();
|
||||
|
||||
|
||||
Kryo kryo = new Kryo();
|
||||
kryo.register(RSAKeyParameters.class, new RsaPublicKeySerializer());
|
||||
kryo.register(RSAPrivateCrtKeyParameters.class, new RsaPrivateKeySerializer());
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
Output output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, public1);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
Input input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
RSAKeyParameters public2 = (RSAKeyParameters) kryo.readClassAndObject(input);
|
||||
|
||||
|
||||
if (!CryptoRSA.compare(public1, public2)) {
|
||||
fail("public keys not equal");
|
||||
}
|
||||
|
||||
|
||||
// Test output to stream, large buffer.
|
||||
outStream = new ByteArrayOutputStream();
|
||||
output = new Output(outStream, 4096);
|
||||
kryo.writeClassAndObject(output, private1);
|
||||
output.flush();
|
||||
|
||||
// Test input from stream, large buffer.
|
||||
input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
|
||||
RSAPrivateCrtKeyParameters private2 = (RSAPrivateCrtKeyParameters) kryo.readClassAndObject(input);
|
||||
|
||||
|
||||
if (!CryptoRSA.compare(private1, private2)) {
|
||||
fail("private keys not equal");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.util.Sys;
|
||||
|
||||
|
||||
public class SCryptTest {
|
||||
|
||||
@Test
|
||||
public void SCrypt() throws IOException, GeneralSecurityException {
|
||||
|
||||
byte[] P, S;
|
||||
int N, r, p, dkLen;
|
||||
String DK;
|
||||
|
||||
// empty key & salt test missing because unsupported by JCE
|
||||
|
||||
P = "password".getBytes("UTF-8");
|
||||
S = "NaCl".getBytes("UTF-8");
|
||||
N = 1024;
|
||||
r = 8;
|
||||
p = 16;
|
||||
dkLen = 64;
|
||||
DK = "FDBABE1C9D3472007856E7190D01E9FE7C6AD7CBC8237830E77376634B3731622EAF30D92E22A3886FF109279D9830DAC727AFB94A83EE6D8360CBDFA2CC0640";
|
||||
|
||||
assertEquals(DK, Sys.bytesToHex(CryptoSCrypt.encrypt(P, S, N, r, p, dkLen)));
|
||||
|
||||
|
||||
P = "pleaseletmein".getBytes("UTF-8");
|
||||
S = "SodiumChloride".getBytes("UTF-8");
|
||||
N = 16384;
|
||||
r = 8;
|
||||
p = 1;
|
||||
dkLen = 64;
|
||||
DK = "7023BDCB3AFD7348461C06CD81FD38EBFDA8FBBA904F8E3EA9B543F6545DA1F2D5432955613F0FCF62D49705242A9AF9E61E85DC0D651E40DFCF017B45575887";
|
||||
|
||||
assertEquals(DK, Sys.bytesToHex(CryptoSCrypt.encrypt(P, S, N, r, p, dkLen)));
|
||||
|
||||
|
||||
P = "pleaseletmein".getBytes("UTF-8");
|
||||
S = "SodiumChloride".getBytes("UTF-8");
|
||||
N = 1048576;
|
||||
r = 8;
|
||||
p = 1;
|
||||
dkLen = 64;
|
||||
DK = "2101CB9B6A511AAEADDBBE09CF70F881EC568D574A2FFD4DABE5EE9820ADAA478E56FD8F4BA5D09FFA1C6D927C40F4C337304049E8A952FBCBF45C6FA77A41A4";
|
||||
|
||||
assertEquals(DK, Sys.bytesToHex(CryptoSCrypt.encrypt(P, S, N, r, p, dkLen)));
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
package dorkbox.util.crypto;
|
||||
|
||||
// Copyright (c) 2006 Damien Miller <djm@mindrot.org>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* JUnit unit tests for BCrypt routines
|
||||
* @author Damien Miller
|
||||
* @version 0.2
|
||||
*/
|
||||
public class TestBCrypt extends TestCase {
|
||||
String test_vectors[][] = {
|
||||
{ "",
|
||||
"$2a$06$DCq7YPn5Rq63x1Lad4cll.",
|
||||
"$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." },
|
||||
{ "",
|
||||
"$2a$08$HqWuK6/Ng6sg9gQzbLrgb.",
|
||||
"$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" },
|
||||
{ "",
|
||||
"$2a$10$k1wbIrmNyFAPwPVPSVa/ze",
|
||||
"$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" },
|
||||
{ "",
|
||||
"$2a$12$k42ZFHFWqBp3vWli.nIn8u",
|
||||
"$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" },
|
||||
{ "a",
|
||||
"$2a$06$m0CrhHm10qJ3lXRY.5zDGO",
|
||||
"$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" },
|
||||
{ "a",
|
||||
"$2a$08$cfcvVd2aQ8CMvoMpP2EBfe",
|
||||
"$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." },
|
||||
{ "a",
|
||||
"$2a$10$k87L/MF28Q673VKh8/cPi.",
|
||||
"$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" },
|
||||
{ "a",
|
||||
"$2a$12$8NJH3LsPrANStV6XtBakCe",
|
||||
"$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" },
|
||||
{ "abc",
|
||||
"$2a$06$If6bvum7DFjUnE9p2uDeDu",
|
||||
"$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" },
|
||||
{ "abc",
|
||||
"$2a$08$Ro0CUfOqk6cXEKf3dyaM7O",
|
||||
"$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" },
|
||||
{ "abc",
|
||||
"$2a$10$WvvTPHKwdBJ3uk0Z37EMR.",
|
||||
"$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" },
|
||||
{ "abc",
|
||||
"$2a$12$EXRkfkdmXn2gzds2SSitu.",
|
||||
"$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz",
|
||||
"$2a$06$.rCVZVOThsIa97pEDOxvGu",
|
||||
"$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz",
|
||||
"$2a$08$aTsUwsyowQuzRrDqFflhge",
|
||||
"$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." },
|
||||
{ "abcdefghijklmnopqrstuvwxyz",
|
||||
"$2a$10$fVH8e28OQRj9tqiDXs1e1u",
|
||||
"$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz",
|
||||
"$2a$12$D4G5f18o7aMMfwasBL7Gpu",
|
||||
"$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
||||
"$2a$06$fPIsBO8qRqkjj273rfaOI.",
|
||||
"$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
||||
"$2a$08$Eq2r4G/76Wv39MzSX262hu",
|
||||
"$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
||||
"$2a$10$LgfYWkbzEvQ4JakH7rOvHe",
|
||||
"$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
||||
"$2a$12$WApznUOJfkEGSmYRfnkrPO",
|
||||
"$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
|
||||
};
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.hashpw(String, String)'
|
||||
*/
|
||||
public void testHashpw() {
|
||||
System.out.print("BCrypt.hashpw(): ");
|
||||
for (int i = 0; i < test_vectors.length; i++) {
|
||||
String plain = test_vectors[i][0];
|
||||
String salt = test_vectors[i][1];
|
||||
String expected = test_vectors[i][2];
|
||||
String hashed = BCrypt.hashpw(plain, salt);
|
||||
assertEquals(hashed, expected);
|
||||
System.out.print(".");
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.gensalt(int)'
|
||||
*/
|
||||
public void testGensaltInt() {
|
||||
System.out.print("BCrypt.gensalt(log_rounds):");
|
||||
for (int i = 4; i <= 12; i++) {
|
||||
System.out.print(" " + Integer.toString(i) + ":");
|
||||
for (int j = 0; j < test_vectors.length; j += 4) {
|
||||
String plain = test_vectors[j][0];
|
||||
String salt = BCrypt.gensalt(i);
|
||||
String hashed1 = BCrypt.hashpw(plain, salt);
|
||||
String hashed2 = BCrypt.hashpw(plain, hashed1);
|
||||
assertEquals(hashed1, hashed2);
|
||||
System.out.print(".");
|
||||
}
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.gensalt()'
|
||||
*/
|
||||
public void testGensalt() {
|
||||
System.out.print("BCrypt.gensalt(): ");
|
||||
for (int i = 0; i < test_vectors.length; i += 4) {
|
||||
String plain = test_vectors[i][0];
|
||||
String salt = BCrypt.gensalt();
|
||||
String hashed1 = BCrypt.hashpw(plain, salt);
|
||||
String hashed2 = BCrypt.hashpw(plain, hashed1);
|
||||
assertEquals(hashed1, hashed2);
|
||||
System.out.print(".");
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.checkpw(String, String)'
|
||||
* expecting success
|
||||
*/
|
||||
public void testCheckpw_success() {
|
||||
System.out.print("BCrypt.checkpw w/ good passwords: ");
|
||||
for (int i = 0; i < test_vectors.length; i++) {
|
||||
String plain = test_vectors[i][0];
|
||||
String expected = test_vectors[i][2];
|
||||
assertTrue(BCrypt.checkpw(plain, expected));
|
||||
System.out.print(".");
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.checkpw(String, String)'
|
||||
* expecting failure
|
||||
*/
|
||||
public void testCheckpw_failure() {
|
||||
System.out.print("BCrypt.checkpw w/ bad passwords: ");
|
||||
for (int i = 0; i < test_vectors.length; i++) {
|
||||
int broken_index = (i + 4) % test_vectors.length;
|
||||
String plain = test_vectors[i][0];
|
||||
String expected = test_vectors[broken_index][2];
|
||||
assertFalse(BCrypt.checkpw(plain, expected));
|
||||
System.out.print(".");
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for correct hashing of non-US-ASCII passwords
|
||||
*/
|
||||
public void testInternationalChars() {
|
||||
System.out.print("BCrypt.hashpw w/ international chars: ");
|
||||
String pw1 = "\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605";
|
||||
String pw2 = "????????";
|
||||
|
||||
String h1 = BCrypt.hashpw(pw1, BCrypt.gensalt());
|
||||
assertFalse(BCrypt.checkpw(pw2, h1));
|
||||
System.out.print(".");
|
||||
|
||||
String h2 = BCrypt.hashpw(pw2, BCrypt.gensalt());
|
||||
assertFalse(BCrypt.checkpw(pw1, h2));
|
||||
System.out.print(".");
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.util.crypto;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class x509Test {
|
||||
|
||||
private static String entropySeed = "asdjhaffasdgfaasttjjhgpx600gn,-356268909087s0dfg4-42kjh255124515hasdg87";
|
||||
|
||||
@Test
|
||||
public void EcdsaCertificate() throws IOException {
|
||||
// create the certificate
|
||||
Calendar expiry = Calendar.getInstance();
|
||||
expiry.add(Calendar.DAY_OF_YEAR, 360);
|
||||
|
||||
Date startDate = new Date(); // time from which certificate is valid
|
||||
Date expiryDate = expiry.getTime(); // time after which certificate is not valid
|
||||
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); // serial number for certificate
|
||||
|
||||
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoECC.generateKeyPair(CryptoECC.p521_curve, new SecureRandom()); // key name from Crypto class
|
||||
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
|
||||
X509CertificateHolder ECDSAx509Certificate = CryptoX509.ECDSA.createCertHolder("SHA384",
|
||||
startDate, expiryDate,
|
||||
new X500Name("CN=Test"), new X500Name("CN=Test"), serialNumber,
|
||||
privateKey, publicKey);
|
||||
// make sure it's a valid cert.
|
||||
if (ECDSAx509Certificate != null) {
|
||||
boolean valid = CryptoX509.ECDSA.validate(ECDSAx509Certificate);
|
||||
|
||||
if (!valid) {
|
||||
fail("Unable to verify a x509 certificate.");
|
||||
}
|
||||
} else {
|
||||
fail("Unable to create a x509 certificate.");
|
||||
}
|
||||
|
||||
// now sign something, then verify the signature.
|
||||
byte[] data = "My keyboard is awesome".getBytes();
|
||||
byte[] signatureBlock = CryptoX509.createSignature(data, ECDSAx509Certificate, privateKey);
|
||||
|
||||
boolean verifySignature = CryptoX509.ECDSA.verifySignature(signatureBlock, publicKey);
|
||||
|
||||
if (!verifySignature) {
|
||||
fail("Unable to verify a x509 certificate signature.");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void DsaCertificate() throws IOException {
|
||||
// create the certificate
|
||||
Calendar expiry = Calendar.getInstance();
|
||||
expiry.add(Calendar.DAY_OF_YEAR, 360);
|
||||
|
||||
Date startDate = new Date(); // time from which certificate is valid
|
||||
Date expiryDate = expiry.getTime(); // time after which certificate is not valid
|
||||
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); // serial number for certificate
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoDSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
|
||||
|
||||
DSAPrivateKeyParameters privateKey = (DSAPrivateKeyParameters) generateKeyPair.getPrivate();
|
||||
DSAPublicKeyParameters publicKey = (DSAPublicKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
|
||||
X509CertificateHolder DSAx509Certificate = CryptoX509.DSA.createCertHolder(startDate, expiryDate,
|
||||
new X500Name("CN=Test"), new X500Name("CN=Test"), serialNumber,
|
||||
privateKey, publicKey);
|
||||
// make sure it's a valid cert.
|
||||
if (DSAx509Certificate != null) {
|
||||
boolean valid = CryptoX509.DSA.validate(DSAx509Certificate);
|
||||
|
||||
if (!valid) {
|
||||
fail("Unable to verify a x509 certificate.");
|
||||
}
|
||||
} else {
|
||||
fail("Unable to create a x509 certificate.");
|
||||
}
|
||||
|
||||
// now sign something, then verify the signature.
|
||||
byte[] data = "My keyboard is awesome".getBytes();
|
||||
byte[] signatureBlock = CryptoX509.createSignature(data, DSAx509Certificate, privateKey);
|
||||
|
||||
boolean verifySignature = CryptoX509.DSA.verifySignature(signatureBlock, publicKey);
|
||||
|
||||
if (!verifySignature) {
|
||||
fail("Unable to verify a x509 certificate signature.");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void RsaCertificate() throws IOException {
|
||||
// create the certificate
|
||||
Calendar expiry = Calendar.getInstance();
|
||||
expiry.add(Calendar.DAY_OF_YEAR, 360);
|
||||
|
||||
Date startDate = new Date(); // time from which certificate is valid
|
||||
Date expiryDate = expiry.getTime(); // time after which certificate is not valid
|
||||
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); // serial number for certificate
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
AsymmetricCipherKeyPair generateKeyPair = CryptoRSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
|
||||
RSAPrivateCrtKeyParameters privateKey = (RSAPrivateCrtKeyParameters) generateKeyPair.getPrivate();
|
||||
RSAKeyParameters publicKey = (RSAKeyParameters) generateKeyPair.getPublic();
|
||||
|
||||
|
||||
X509CertificateHolder RSAx509Certificate = CryptoX509.RSA.createCertHolder(startDate, expiryDate,
|
||||
new X500Name("CN=Test"), new X500Name("CN=Test"), serialNumber,
|
||||
privateKey, publicKey);
|
||||
// make sure it's a valid cert.
|
||||
if (RSAx509Certificate != null) {
|
||||
boolean valid = CryptoX509.RSA.validate(RSAx509Certificate);
|
||||
|
||||
if (!valid) {
|
||||
fail("Unable to verify a x509 certificate.");
|
||||
}
|
||||
} else {
|
||||
fail("Unable to create a x509 certificate.");
|
||||
}
|
||||
|
||||
// now sign something, then verify the signature.
|
||||
byte[] data = "My keyboard is awesome".getBytes();
|
||||
byte[] signatureBlock = CryptoX509.createSignature(data, RSAx509Certificate, privateKey);
|
||||
|
||||
boolean verifySignature = CryptoX509.RSA.verifySignature(signatureBlock, publicKey);
|
||||
|
||||
if (!verifySignature) {
|
||||
fail("Unable to verify a x509 certificate signature.");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue