├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pom.xml
├── settings.gradle
└── src
├── main
└── java
│ └── com
│ └── starkbank
│ └── ellipticcurve
│ ├── Curve.java
│ ├── Ecdsa.java
│ ├── Math.java
│ ├── Point.java
│ ├── PrivateKey.java
│ ├── PublicKey.java
│ ├── Signature.java
│ └── utils
│ ├── Base64.java
│ ├── BinaryAscii.java
│ ├── ByteString.java
│ ├── Der.java
│ ├── File.java
│ └── RandomInteger.java
└── test
├── java
└── com
│ └── starkbank
│ └── ellipticcurve
│ ├── EcdsaTest.java
│ ├── OpenSSLTest.java
│ ├── PrivateKeyTest.java
│ ├── PublicKeyTest.java
│ ├── SignatureTest.java
│ └── Utils.java
└── resources
├── message.txt
├── privateKey.pem
├── publicKey.pem
├── signature.binary
└── signatureBinary.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | ##########################
2 | ## Java
3 | ##########################
4 | *.class
5 | .mtj.tmp/
6 | *.war
7 | *.ear
8 | hs_err_pid*
9 |
10 | ##########################
11 | ## Maven
12 | ##########################
13 | target/
14 | pom.xml.tag
15 | pom.xml.releaseBackup
16 | pom.xml.versionsBackup
17 | pom.xml.next
18 | release.properties
19 |
20 | # Ignore Gradle project-specific cache directory
21 | .gradle
22 |
23 | # Ignore Gradle build output directory
24 | build
25 |
26 | ##########################
27 | ## IntelliJ
28 | ##########################
29 | *.iml
30 | .idea/
31 | *.ipr
32 | *.iws
33 | out/
34 | .idea_modules/
35 |
36 | ##########################
37 | ## Eclipse
38 | ##########################
39 | .metadata
40 | .classpath
41 | .project
42 | .settings/
43 | bin/
44 | tmp/
45 | *.tmp
46 | *.bak
47 | *.swp
48 | *~.nib
49 | local.properties
50 | .loadpath
51 |
52 | ##########################
53 | ## NetBeans
54 | ##########################
55 | nbproject/private/
56 | build/
57 | nbbuild/
58 | dist/
59 | nbdist/
60 | nbactions.xml
61 | nb-configuration.xml
62 |
63 | ##########################
64 | ## OS X
65 | ##########################
66 | .DS_Store
67 |
68 | ##########################
69 | ## Example Files
70 | ##########################
71 | src/main/java/Generate*
72 | message.txt
73 | publicKey.pem
74 | privateKey.pem
75 | signatureBinary.txt
76 | gradle.properties
77 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | install: true
3 |
4 | matrix:
5 | include:
6 | - jdk: openjdk8
7 | - jdk: openjdk9
8 | - jdk: openjdk10
9 | - jdk: openjdk11
10 | - jdk: openjdk12
11 | - jdk: openjdk13
12 | - jdk: openjdk14
13 | - jdk: openjdk-ea
14 | allow_failures:
15 | - jdk: openjdk-ea
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to the following versioning pattern:
7 |
8 | Given a version number MAJOR.MINOR.PATCH, increment:
9 |
10 | - MAJOR version when **breaking changes** are introduced;
11 | - MINOR version when **backwards compatible changes** are introduced;
12 | - PATCH version when backwards compatible bug **fixes** are implemented.
13 |
14 |
15 | ## [Unreleased]
16 |
17 | ### Fixed
18 | - groupId in pom.xml
19 |
20 | ## [1.0.2] - 2021-11-09
21 | ### Fixed
22 | - point at infinity verification in signature and public key
23 |
24 | ## [1.0.1] - 2021-11-04
25 | ### Fixed
26 | - Signature r and s range check
27 |
28 | ## [1.0.0] - 2020-04-22
29 | ### Added
30 | - first official version
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Stark Bank S.A.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## A lightweight and fast ECDSA
2 |
3 | ### Overview
4 |
5 | This is a pure Java implementation of the Elliptic Curve Digital Signature Algorithm (ECDSA). It is compatible with Java 8+ and OpenSSL. It uses some elegant math such as Jacobian Coordinates to speed up the ECDSA.
6 |
7 | ### Installation
8 |
9 | #### Maven Central
10 | In pom.xml:
11 |
12 | ```xml
13 |
Encodes and decodes to and from Base64 notation.
5 | *Homepage: http://iharder.net/base64.
6 | * 7 | *Example:
8 | * 9 | *String encoded = Base64.encode( myByteArray );
10 | * byte[] myByteArray = Base64.decode( encoded );
11 | *
12 | * The options parameter, which appears in a few places, is used to pass 13 | * several pieces of information to the encoder. In the "higher level" methods such as 14 | * encodeBytes( bytes, options ) the options parameter can be used to indicate such 15 | * things as first gzipping the bytes before encoding them, not inserting linefeeds, 16 | * and encoding using the URL-safe and Ordered dialects.
17 | * 18 | *Note, according to RFC3548, 19 | * Section 2.1, implementations should not add line feeds unless explicitly told 20 | * to do so. I've got Base64 set to this behavior now, although earlier versions 21 | * broke lines by default.
22 | * 23 | *The constants defined in Base64 can be OR-ed together to combine options, so you 24 | * might make a call like this:
25 | * 26 | *String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES );
27 | * to compress the data before encoding it and then making the output have newline characters.
28 | *Also...
29 | *String encoded = Base64.encodeBytes( crazyString.getBytes() );
30 | *
31 | *
32 | *
33 | * 34 | * Change Log: 35 | *
36 | *138 | * I am placing this code in the Public Domain. Do with it as you will. 139 | * This software comes with no guarantees or warranties but with 140 | * plenty of well-wishing instead! 141 | * Please visit http://iharder.net/base64 142 | * periodically to check for updates or to contribute improvements. 143 | *
144 | * 145 | * @author Robert Harder 146 | * @author rob@iharder.net 147 | * @version 2.3.7 148 | */ 149 | public class Base64 150 | { 151 | 152 | /* ******** P U B L I C F I E L D S ******** */ 153 | 154 | 155 | /** No options specified. Value is zero. */ 156 | public final static int NO_OPTIONS = 0; 157 | 158 | /** Specify encoding in first bit. Value is one. */ 159 | public final static int ENCODE = 1; 160 | 161 | 162 | /** Specify decoding in first bit. Value is zero. */ 163 | public final static int DECODE = 0; 164 | 165 | 166 | /** Specify that data should be gzip-compressed in second bit. Value is two. */ 167 | public final static int GZIP = 2; 168 | 169 | /** Specify that gzipped data should not be automatically gunzipped. */ 170 | public final static int DONT_GUNZIP = 4; 171 | 172 | 173 | /** Do break lines when encoding. Value is 8. */ 174 | public final static int DO_BREAK_LINES = 8; 175 | 176 | /** 177 | * Encode using Base64-like encoding that is URL- and Filename-safe as described 178 | * in Section 4 of RFC3548: 179 | * http://www.faqs.org/rfcs/rfc3548.html. 180 | * It is important to note that data encoded this way is not officially valid Base64, 181 | * or at the very least should not be called Base64 without also specifying that is 182 | * was encoded using the URL- and Filename-safe dialect. 183 | */ 184 | public final static int URL_SAFE = 16; 185 | 186 | 187 | /** 188 | * Encode using the special "ordered" dialect of Base64 described here: 189 | * http://www.faqs.org/qa/rfcc-1940.html. 190 | */ 191 | public final static int ORDERED = 32; 192 | 193 | 194 | /* ******** P R I V A T E F I E L D S ******** */ 195 | 196 | 197 | /** Maximum line length (76) of Base64 output. */ 198 | private final static int MAX_LINE_LENGTH = 76; 199 | 200 | 201 | /** The equals sign (=) as a byte. */ 202 | private final static byte EQUALS_SIGN = (byte)'='; 203 | 204 | 205 | /** The new line character (\n) as a byte. */ 206 | private final static byte NEW_LINE = (byte)'\n'; 207 | 208 | 209 | /** Preferred encoding. */ 210 | private final static String PREFERRED_ENCODING = "US-ASCII"; 211 | 212 | 213 | private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding 214 | private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding 215 | 216 | 217 | /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ 218 | 219 | /** The 64 valid Base64 values. */ 220 | /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ 221 | private final static byte[] _STANDARD_ALPHABET = { 222 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 223 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 224 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 225 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 226 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 227 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 228 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 229 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', 230 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 231 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' 232 | }; 233 | 234 | 235 | /** 236 | * Translates a Base64 value to either its 6-bit reconstruction value 237 | * or a negative number indicating some other meaning. 238 | **/ 239 | private final static byte[] _STANDARD_DECODABET = { 240 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 241 | -5,-5, // Whitespace: Tab and Linefeed 242 | -9,-9, // Decimal 11 - 12 243 | -5, // Whitespace: Carriage Return 244 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 245 | -9,-9,-9,-9,-9, // Decimal 27 - 31 246 | -5, // Whitespace: Space 247 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 248 | 62, // Plus sign at decimal 43 249 | -9,-9,-9, // Decimal 44 - 46 250 | 63, // Slash at decimal 47 251 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine 252 | -9,-9,-9, // Decimal 58 - 60 253 | -1, // Equals sign at decimal 61 254 | -9,-9,-9, // Decimal 62 - 64 255 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' 256 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' 257 | -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 258 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' 259 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' 260 | -9,-9,-9,-9,-9 // Decimal 123 - 127 261 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 262 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 263 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 264 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 265 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 266 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 267 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 268 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 269 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 270 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 271 | }; 272 | 273 | 274 | /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ 275 | 276 | /** 277 | * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: 278 | * http://www.faqs.org/rfcs/rfc3548.html. 279 | * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." 280 | */ 281 | private final static byte[] _URL_SAFE_ALPHABET = { 282 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 283 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 284 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 285 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 286 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 287 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 288 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 289 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', 290 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 291 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' 292 | }; 293 | 294 | /** 295 | * Used in decoding URL- and Filename-safe dialects of Base64. 296 | */ 297 | private final static byte[] _URL_SAFE_DECODABET = { 298 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 299 | -5,-5, // Whitespace: Tab and Linefeed 300 | -9,-9, // Decimal 11 - 12 301 | -5, // Whitespace: Carriage Return 302 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 303 | -9,-9,-9,-9,-9, // Decimal 27 - 31 304 | -5, // Whitespace: Space 305 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 306 | -9, // Plus sign at decimal 43 307 | -9, // Decimal 44 308 | 62, // Minus sign at decimal 45 309 | -9, // Decimal 46 310 | -9, // Slash at decimal 47 311 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine 312 | -9,-9,-9, // Decimal 58 - 60 313 | -1, // Equals sign at decimal 61 314 | -9,-9,-9, // Decimal 62 - 64 315 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' 316 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' 317 | -9,-9,-9,-9, // Decimal 91 - 94 318 | 63, // Underscore at decimal 95 319 | -9, // Decimal 96 320 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' 321 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' 322 | -9,-9,-9,-9,-9 // Decimal 123 - 127 323 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 324 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 325 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 326 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 327 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 328 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 329 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 330 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 331 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 332 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 333 | }; 334 | 335 | 336 | 337 | /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ 338 | 339 | /** 340 | * I don't get the point of this technique, but someone requested it, 341 | * and it is described here: 342 | * http://www.faqs.org/qa/rfcc-1940.html. 343 | */ 344 | private final static byte[] _ORDERED_ALPHABET = { 345 | (byte)'-', 346 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', 347 | (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', 348 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 349 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 350 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 351 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 352 | (byte)'_', 353 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 354 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 355 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 356 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' 357 | }; 358 | 359 | /** 360 | * Used in decoding the "ordered" dialect of Base64. 361 | */ 362 | private final static byte[] _ORDERED_DECODABET = { 363 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 364 | -5,-5, // Whitespace: Tab and Linefeed 365 | -9,-9, // Decimal 11 - 12 366 | -5, // Whitespace: Carriage Return 367 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 368 | -9,-9,-9,-9,-9, // Decimal 27 - 31 369 | -5, // Whitespace: Space 370 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 371 | -9, // Plus sign at decimal 43 372 | -9, // Decimal 44 373 | 0, // Minus sign at decimal 45 374 | -9, // Decimal 46 375 | -9, // Slash at decimal 47 376 | 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine 377 | -9,-9,-9, // Decimal 58 - 60 378 | -1, // Equals sign at decimal 61 379 | -9,-9,-9, // Decimal 62 - 64 380 | 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' 381 | 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' 382 | -9,-9,-9,-9, // Decimal 91 - 94 383 | 37, // Underscore at decimal 95 384 | -9, // Decimal 96 385 | 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' 386 | 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' 387 | -9,-9,-9,-9,-9 // Decimal 123 - 127 388 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 389 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 390 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 391 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 392 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 393 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 394 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 395 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 396 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 397 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 398 | }; 399 | 400 | 401 | /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ 402 | 403 | 404 | /** 405 | * Returns one of the _SOMETHING_ALPHABET byte arrays depending on 406 | * the options specified. 407 | * It's possible, though silly, to specify ORDERED and URLSAFE 408 | * in which case one of them will be picked, though there is 409 | * no guarantee as to which one will be picked. 410 | */ 411 | private final static byte[] getAlphabet( int options ) { 412 | if ((options & URL_SAFE) == URL_SAFE) { 413 | return _URL_SAFE_ALPHABET; 414 | } else if ((options & ORDERED) == ORDERED) { 415 | return _ORDERED_ALPHABET; 416 | } else { 417 | return _STANDARD_ALPHABET; 418 | } 419 | } // end getAlphabet 420 | 421 | 422 | /** 423 | * Returns one of the _SOMETHING_DECODABET byte arrays depending on 424 | * the options specified. 425 | * It's possible, though silly, to specify ORDERED and URL_SAFE 426 | * in which case one of them will be picked, though there is 427 | * no guarantee as to which one will be picked. 428 | */ 429 | private final static byte[] getDecodabet( int options ) { 430 | if( (options & URL_SAFE) == URL_SAFE) { 431 | return _URL_SAFE_DECODABET; 432 | } else if ((options & ORDERED) == ORDERED) { 433 | return _ORDERED_DECODABET; 434 | } else { 435 | return _STANDARD_DECODABET; 436 | } 437 | } // end getAlphabet 438 | 439 | 440 | 441 | /** Defeats instantiation. */ 442 | public Base64(){} 443 | 444 | 445 | 446 | 447 | /* ******** E N C O D I N G M E T H O D S ******** */ 448 | 449 | 450 | /** 451 | * Encodes up to the first three bytes of array threeBytes 452 | * and returns a four-byte array in Base64 notation. 453 | * The actual number of significant bytes in your array is 454 | * given by numSigBytes. 455 | * The array threeBytes needs only be as big as 456 | * numSigBytes. 457 | * Code can reuse a byte array by passing a four-byte array as b4. 458 | * 459 | * @param b4 A reusable byte array to reduce array instantiation 460 | * @param threeBytes the array to convert 461 | * @param numSigBytes the number of significant bytes in your array 462 | * @return four byte array in Base64 notation. 463 | * @since 1.5.1 464 | */ 465 | private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { 466 | encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); 467 | return b4; 468 | } // end encode3to4 469 | 470 | 471 | /** 472 | *Encodes up to three bytes of the array source 473 | * and writes the resulting four Base64 bytes to destination. 474 | * The source and destination arrays can be manipulated 475 | * anywhere along their length by specifying 476 | * srcOffset and destOffset. 477 | * This method does not check to make sure your arrays 478 | * are large enough to accomodate srcOffset + 3 for 479 | * the source array or destOffset + 4 for 480 | * the destination array. 481 | * The actual number of significant bytes in your array is 482 | * given by numSigBytes.
483 | *This is the lowest level of the encoding methods with 484 | * all possible parameters.
485 | * 486 | * @param source the array to convert 487 | * @param srcOffset the index where conversion begins 488 | * @param numSigBytes the number of significant bytes in your array 489 | * @param destination the array to hold the conversion 490 | * @param destOffset the index where output will be put 491 | * @return the destination array 492 | * @since 1.3 493 | */ 494 | private static byte[] encode3to4( 495 | byte[] source, int srcOffset, int numSigBytes, 496 | byte[] destination, int destOffset, int options ) { 497 | 498 | byte[] ALPHABET = getAlphabet( options ); 499 | 500 | // 1 2 3 501 | // 01234567890123456789012345678901 Bit position 502 | // --------000000001111111122222222 Array position from threeBytes 503 | // --------| || || || | Six bit groups to index ALPHABET 504 | // >>18 >>12 >> 6 >> 0 Right shift necessary 505 | // 0x3f 0x3f 0x3f Additional AND 506 | 507 | // Create buffer with zero-padding if there are only one or two 508 | // significant bytes passed in the array. 509 | // We have to shift left 24 in order to flush out the 1's that appear 510 | // when Java treats a value as negative that is cast from a byte to an int. 511 | int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) 512 | | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) 513 | | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); 514 | 515 | switch( numSigBytes ) 516 | { 517 | case 3: 518 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 519 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 520 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; 521 | destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; 522 | return destination; 523 | 524 | case 2: 525 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 526 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 527 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; 528 | destination[ destOffset + 3 ] = EQUALS_SIGN; 529 | return destination; 530 | 531 | case 1: 532 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 533 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 534 | destination[ destOffset + 2 ] = EQUALS_SIGN; 535 | destination[ destOffset + 3 ] = EQUALS_SIGN; 536 | return destination; 537 | 538 | default: 539 | return destination; 540 | } // end switch 541 | } // end encode3to4 542 | 543 | 544 | 545 | /** 546 | * Performs Base64 encoding on theraw
ByteBuffer,
547 | * writing it to the encoded
ByteBuffer.
548 | * This is an experimental feature. Currently it does not
549 | * pass along any options (such as {@link #DO_BREAK_LINES}
550 | * or {@link #GZIP}.
551 | *
552 | * @param raw input buffer
553 | * @param encoded output buffer
554 | * @since 2.3
555 | */
556 | public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){
557 | byte[] raw3 = new byte[3];
558 | byte[] enc4 = new byte[4];
559 |
560 | while( raw.hasRemaining() ){
561 | int rem = java.lang.Math.min(3,raw.remaining());
562 | raw.get(raw3,0,rem);
563 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS );
564 | encoded.put(enc4);
565 | } // end input remaining
566 | }
567 |
568 |
569 | /**
570 | * Performs Base64 encoding on the raw
ByteBuffer,
571 | * writing it to the encoded
CharBuffer.
572 | * This is an experimental feature. Currently it does not
573 | * pass along any options (such as {@link #DO_BREAK_LINES}
574 | * or {@link #GZIP}.
575 | *
576 | * @param raw input buffer
577 | * @param encoded output buffer
578 | * @since 2.3
579 | */
580 | public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){
581 | byte[] raw3 = new byte[3];
582 | byte[] enc4 = new byte[4];
583 |
584 | while( raw.hasRemaining() ){
585 | int rem = java.lang.Math.min(3,raw.remaining());
586 | raw.get(raw3,0,rem);
587 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS );
588 | for( int i = 0; i < 4; i++ ){
589 | encoded.put( (char)(enc4[i] & 0xFF) );
590 | }
591 | } // end input remaining
592 | }
593 |
594 |
595 |
596 |
597 | /**
598 | * Serializes an object and returns the Base64-encoded
599 | * version of that serialized object.
600 | *
601 | * As of v 2.3, if the object 602 | * cannot be serialized or there is another error, 603 | * the method will throw an java.io.IOException. This is new to v2.3! 604 | * In earlier versions, it just returned a null value, but 605 | * in retrospect that's a pretty poor way to handle it.
606 | * 607 | * The object is not GZip-compressed before being encoded. 608 | * 609 | * @param serializableObject The object to encode 610 | * @return The Base64-encoded object 611 | * @throws java.io.IOException if there is an error 612 | * @throws NullPointerException if serializedObject is null 613 | * @since 1.4 614 | */ 615 | public static String encodeObject( java.io.Serializable serializableObject ) 616 | throws java.io.IOException { 617 | return encodeObject( serializableObject, NO_OPTIONS ); 618 | } // end encodeObject 619 | 620 | 621 | 622 | /** 623 | * Serializes an object and returns the Base64-encoded 624 | * version of that serialized object. 625 | * 626 | *As of v 2.3, if the object 627 | * cannot be serialized or there is another error, 628 | * the method will throw an java.io.IOException. This is new to v2.3! 629 | * In earlier versions, it just returned a null value, but 630 | * in retrospect that's a pretty poor way to handle it.
631 | * 632 | * The object is not GZip-compressed before being encoded. 633 | *634 | * Example options:
635 | * GZIP: gzip-compresses object before encoding it. 636 | * DO_BREAK_LINES: break lines at 76 characters 637 | *638 | *
639 | * Example: encodeObject( myObj, Base64.GZIP )
or
640 | *
641 | * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )
642 | *
643 | * @param serializableObject The object to encode
644 | * @param options Specified options
645 | * @return The Base64-encoded object
646 | * @see Base64#GZIP
647 | * @see Base64#DO_BREAK_LINES
648 | * @throws java.io.IOException if there is an error
649 | * @since 2.0
650 | */
651 | public static String encodeObject( java.io.Serializable serializableObject, int options )
652 | throws java.io.IOException {
653 |
654 | if( serializableObject == null ){
655 | throw new NullPointerException( "Cannot serialize a null object." );
656 | } // end if: null
657 |
658 | // Streams
659 | java.io.ByteArrayOutputStream baos = null;
660 | java.io.OutputStream b64os = null;
661 | java.util.zip.GZIPOutputStream gzos = null;
662 | java.io.ObjectOutputStream oos = null;
663 |
664 |
665 | try {
666 | // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
667 | baos = new java.io.ByteArrayOutputStream();
668 | b64os = new OutputStream( baos, ENCODE | options );
669 | if( (options & GZIP) != 0 ){
670 | // Gzip
671 | gzos = new java.util.zip.GZIPOutputStream(b64os);
672 | oos = new java.io.ObjectOutputStream( gzos );
673 | } else {
674 | // Not gzipped
675 | oos = new java.io.ObjectOutputStream( b64os );
676 | }
677 | oos.writeObject( serializableObject );
678 | } // end try
679 | catch( java.io.IOException e ) {
680 | // Catch it and then throw it immediately so that
681 | // the finally{} block is called for cleanup.
682 | throw e;
683 | } // end catch
684 | finally {
685 | try{ oos.close(); } catch( Exception e ){}
686 | try{ gzos.close(); } catch( Exception e ){}
687 | try{ b64os.close(); } catch( Exception e ){}
688 | try{ baos.close(); } catch( Exception e ){}
689 | } // end finally
690 |
691 | // Return value according to relevant encoding.
692 | try {
693 | return new String( baos.toByteArray(), PREFERRED_ENCODING );
694 | } // end try
695 | catch (java.io.UnsupportedEncodingException uue){
696 | // Fall back to some Java default
697 | return new String( baos.toByteArray() );
698 | } // end catch
699 |
700 | } // end encode
701 |
702 |
703 |
704 | /**
705 | * Encodes a byte array into Base64 notation.
706 | * Does not GZip-compress data.
707 | *
708 | * @param source The data to convert
709 | * @return The data in Base64-encoded form
710 | * @throws NullPointerException if source array is null
711 | * @since 1.4
712 | */
713 | public static String encodeBytes( byte[] source ) {
714 | // Since we're not going to have the GZIP encoding turned on,
715 | // we're not going to have an java.io.IOException thrown, so
716 | // we should not force the user to have to catch it.
717 | String encoded = null;
718 | try {
719 | encoded = encodeBytes(source, 0, source.length, NO_OPTIONS);
720 | } catch (java.io.IOException ex) {
721 | assert false : ex.getMessage();
722 | } // end catch
723 | assert encoded != null;
724 | return encoded;
725 | } // end encodeBytes
726 |
727 |
728 |
729 | /**
730 | * Encodes a byte array into Base64 notation.
731 | *
732 | * Example options:
733 | * GZIP: gzip-compresses object before encoding it. 734 | * DO_BREAK_LINES: break lines at 76 characters 735 | * Note: Technically, this makes your encoding non-compliant. 736 | *737 | *
738 | * Example: encodeBytes( myData, Base64.GZIP )
or
739 | *
740 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )
741 | *
742 | *
743 | *
As of v 2.3, if there is an error with the GZIP stream, 744 | * the method will throw an java.io.IOException. This is new to v2.3! 745 | * In earlier versions, it just returned a null value, but 746 | * in retrospect that's a pretty poor way to handle it.
747 | * 748 | * 749 | * @param source The data to convert 750 | * @param options Specified options 751 | * @return The Base64-encoded data as a String 752 | * @see Base64#GZIP 753 | * @see Base64#DO_BREAK_LINES 754 | * @throws java.io.IOException if there is an error 755 | * @throws NullPointerException if source array is null 756 | * @since 2.0 757 | */ 758 | public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { 759 | return encodeBytes( source, 0, source.length, options ); 760 | } // end encodeBytes 761 | 762 | 763 | /** 764 | * Encodes a byte array into Base64 notation. 765 | * Does not GZip-compress data. 766 | * 767 | *As of v 2.3, if there is an error, 768 | * the method will throw an java.io.IOException. This is new to v2.3! 769 | * In earlier versions, it just returned a null value, but 770 | * in retrospect that's a pretty poor way to handle it.
771 | * 772 | * 773 | * @param source The data to convert 774 | * @param off Offset in array where conversion should begin 775 | * @param len Length of data to convert 776 | * @return The Base64-encoded data as a String 777 | * @throws NullPointerException if source array is null 778 | * @throws IllegalArgumentException if source array, offset, or length are invalid 779 | * @since 1.4 780 | */ 781 | public static String encodeBytes( byte[] source, int off, int len ) { 782 | // Since we're not going to have the GZIP encoding turned on, 783 | // we're not going to have an java.io.IOException thrown, so 784 | // we should not force the user to have to catch it. 785 | String encoded = null; 786 | try { 787 | encoded = encodeBytes( source, off, len, NO_OPTIONS ); 788 | } catch (java.io.IOException ex) { 789 | assert false : ex.getMessage(); 790 | } // end catch 791 | assert encoded != null; 792 | return encoded; 793 | } // end encodeBytes 794 | 795 | 796 | 797 | /** 798 | * Encodes a byte array into Base64 notation. 799 | *800 | * Example options:
801 | * GZIP: gzip-compresses object before encoding it. 802 | * DO_BREAK_LINES: break lines at 76 characters 803 | * Note: Technically, this makes your encoding non-compliant. 804 | *805 | *
806 | * Example: encodeBytes( myData, Base64.GZIP )
or
807 | *
808 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )
809 | *
810 | *
811 | *
As of v 2.3, if there is an error with the GZIP stream, 812 | * the method will throw an java.io.IOException. This is new to v2.3! 813 | * In earlier versions, it just returned a null value, but 814 | * in retrospect that's a pretty poor way to handle it.
815 | * 816 | * 817 | * @param source The data to convert 818 | * @param off Offset in array where conversion should begin 819 | * @param len Length of data to convert 820 | * @param options Specified options 821 | * @return The Base64-encoded data as a String 822 | * @see Base64#GZIP 823 | * @see Base64#DO_BREAK_LINES 824 | * @throws java.io.IOException if there is an error 825 | * @throws NullPointerException if source array is null 826 | * @throws IllegalArgumentException if source array, offset, or length are invalid 827 | * @since 2.0 828 | */ 829 | public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { 830 | byte[] encoded = encodeBytesToBytes( source, off, len, options ); 831 | 832 | // Return value according to relevant encoding. 833 | try { 834 | return new String( encoded, PREFERRED_ENCODING ); 835 | } // end try 836 | catch (java.io.UnsupportedEncodingException uue) { 837 | return new String( encoded ); 838 | } // end catch 839 | 840 | } // end encodeBytes 841 | 842 | 843 | 844 | 845 | /** 846 | * Similar to {@link #encodeBytes(byte[])} but returns 847 | * a byte array instead of instantiating a String. This is more efficient 848 | * if you're working with I/O streams and have large data sets to encode. 849 | * 850 | * 851 | * @param source The data to convert 852 | * @return The Base64-encoded data as a byte[] (of ASCII characters) 853 | * @throws NullPointerException if source array is null 854 | * @since 2.3.1 855 | */ 856 | public static byte[] encodeBytesToBytes( byte[] source ) { 857 | byte[] encoded = null; 858 | try { 859 | encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); 860 | } catch( java.io.IOException ex ) { 861 | assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); 862 | } 863 | return encoded; 864 | } 865 | 866 | 867 | /** 868 | * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns 869 | * a byte array instead of instantiating a String. This is more efficient 870 | * if you're working with I/O streams and have large data sets to encode. 871 | * 872 | * 873 | * @param source The data to convert 874 | * @param off Offset in array where conversion should begin 875 | * @param len Length of data to convert 876 | * @param options Specified options 877 | * @return The Base64-encoded data as a String 878 | * @see Base64#GZIP 879 | * @see Base64#DO_BREAK_LINES 880 | * @throws java.io.IOException if there is an error 881 | * @throws NullPointerException if source array is null 882 | * @throws IllegalArgumentException if source array, offset, or length are invalid 883 | * @since 2.3.1 884 | */ 885 | public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { 886 | 887 | if( source == null ){ 888 | throw new NullPointerException( "Cannot serialize a null array." ); 889 | } // end if: null 890 | 891 | if( off < 0 ){ 892 | throw new IllegalArgumentException( "Cannot have negative offset: " + off ); 893 | } // end if: off < 0 894 | 895 | if( len < 0 ){ 896 | throw new IllegalArgumentException( "Cannot have length offset: " + len ); 897 | } // end if: len < 0 898 | 899 | if( off + len > source.length ){ 900 | throw new IllegalArgumentException( 901 | String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); 902 | } // end if: off < 0 903 | 904 | 905 | 906 | // Compress? 907 | if( (options & GZIP) != 0 ) { 908 | java.io.ByteArrayOutputStream baos = null; 909 | java.util.zip.GZIPOutputStream gzos = null; 910 | OutputStream b64os = null; 911 | 912 | try { 913 | // GZip -> Base64 -> ByteArray 914 | baos = new java.io.ByteArrayOutputStream(); 915 | b64os = new OutputStream( baos, ENCODE | options ); 916 | gzos = new java.util.zip.GZIPOutputStream( b64os ); 917 | 918 | gzos.write( source, off, len ); 919 | gzos.close(); 920 | } // end try 921 | catch( java.io.IOException e ) { 922 | // Catch it and then throw it immediately so that 923 | // the finally{} block is called for cleanup. 924 | throw e; 925 | } // end catch 926 | finally { 927 | try{ gzos.close(); } catch( Exception e ){} 928 | try{ b64os.close(); } catch( Exception e ){} 929 | try{ baos.close(); } catch( Exception e ){} 930 | } // end finally 931 | 932 | return baos.toByteArray(); 933 | } // end if: compress 934 | 935 | // Else, don't compress. Better not to use streams at all then. 936 | else { 937 | boolean breakLines = (options & DO_BREAK_LINES) != 0; 938 | 939 | //int len43 = len * 4 / 3; 940 | //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 941 | // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding 942 | // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines 943 | // Try to determine more precisely how big the array needs to be. 944 | // If we get it right, we don't have to do an array copy, and 945 | // we save a bunch of memory. 946 | int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding 947 | if( breakLines ){ 948 | encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters 949 | } 950 | byte[] outBuff = new byte[ encLen ]; 951 | 952 | 953 | int d = 0; 954 | int e = 0; 955 | int len2 = len - 2; 956 | int lineLength = 0; 957 | for( ; d < len2; d+=3, e+=4 ) { 958 | encode3to4( source, d+off, 3, outBuff, e, options ); 959 | 960 | lineLength += 4; 961 | if( breakLines && lineLength >= MAX_LINE_LENGTH ) 962 | { 963 | outBuff[e+4] = NEW_LINE; 964 | e++; 965 | lineLength = 0; 966 | } // end if: end of line 967 | } // en dfor: each piece of array 968 | 969 | if( d < len ) { 970 | encode3to4( source, d+off, len - d, outBuff, e, options ); 971 | e += 4; 972 | } // end if: some padding needed 973 | 974 | 975 | // Only resize array if we didn't guess it right. 976 | if( e <= outBuff.length - 1 ){ 977 | // If breaking lines and the last byte falls right at 978 | // the line length (76 bytes per line), there will be 979 | // one extra byte, and the array will need to be resized. 980 | // Not too bad of an estimate on array size, I'd say. 981 | byte[] finalOut = new byte[e]; 982 | System.arraycopy(outBuff,0, finalOut,0,e); 983 | //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); 984 | return finalOut; 985 | } else { 986 | //System.err.println("No need to resize array."); 987 | return outBuff; 988 | } 989 | 990 | } // end else: don't compress 991 | 992 | } // end encodeBytesToBytes 993 | 994 | 995 | 996 | 997 | 998 | /* ******** D E C O D I N G M E T H O D S ******** */ 999 | 1000 | 1001 | /** 1002 | * Decodes four bytes from array source 1003 | * and writes the resulting bytes (up to three of them) 1004 | * to destination. 1005 | * The source and destination arrays can be manipulated 1006 | * anywhere along their length by specifying 1007 | * srcOffset and destOffset. 1008 | * This method does not check to make sure your arrays 1009 | * are large enough to accomodate srcOffset + 4 for 1010 | * the source array or destOffset + 3 for 1011 | * the destination array. 1012 | * This method returns the actual number of bytes that 1013 | * were converted from the Base64 encoding. 1014 | *This is the lowest level of the decoding methods with 1015 | * all possible parameters.
1016 | * 1017 | * 1018 | * @param source the array to convert 1019 | * @param srcOffset the index where conversion begins 1020 | * @param destination the array to hold the conversion 1021 | * @param destOffset the index where output will be put 1022 | * @param options alphabet type is pulled from this (standard, url-safe, ordered) 1023 | * @return the number of decoded bytes converted 1024 | * @throws NullPointerException if source or destination arrays are null 1025 | * @throws IllegalArgumentException if srcOffset or destOffset are invalid 1026 | * or there is not enough room in the array. 1027 | * @since 1.3 1028 | */ 1029 | private static int decode4to3( 1030 | byte[] source, int srcOffset, 1031 | byte[] destination, int destOffset, int options ) { 1032 | 1033 | // Lots of error checking and exception throwing 1034 | if( source == null ){ 1035 | throw new NullPointerException( "Source array was null." ); 1036 | } // end if 1037 | if( destination == null ){ 1038 | throw new NullPointerException( "Destination array was null." ); 1039 | } // end if 1040 | if( srcOffset < 0 || srcOffset + 3 >= source.length ){ 1041 | throw new IllegalArgumentException( String.format( 1042 | "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); 1043 | } // end if 1044 | if( destOffset < 0 || destOffset +2 >= destination.length ){ 1045 | throw new IllegalArgumentException( String.format( 1046 | "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); 1047 | } // end if 1048 | 1049 | 1050 | byte[] DECODABET = getDecodabet( options ); 1051 | 1052 | // Example: Dk== 1053 | if( source[ srcOffset + 2] == EQUALS_SIGN ) { 1054 | // Two ways to do the same thing. Don't know which way I like best. 1055 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1056 | // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); 1057 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1058 | | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); 1059 | 1060 | destination[ destOffset ] = (byte)( outBuff >>> 16 ); 1061 | return 1; 1062 | } 1063 | 1064 | // Example: DkL= 1065 | else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { 1066 | // Two ways to do the same thing. Don't know which way I like best. 1067 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1068 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1069 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); 1070 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1071 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) 1072 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); 1073 | 1074 | destination[ destOffset ] = (byte)( outBuff >>> 16 ); 1075 | destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); 1076 | return 2; 1077 | } 1078 | 1079 | // Example: DkLE 1080 | else { 1081 | // Two ways to do the same thing. Don't know which way I like best. 1082 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1083 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1084 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) 1085 | // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); 1086 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1087 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) 1088 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) 1089 | | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); 1090 | 1091 | 1092 | destination[ destOffset ] = (byte)( outBuff >> 16 ); 1093 | destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); 1094 | destination[ destOffset + 2 ] = (byte)( outBuff ); 1095 | 1096 | return 3; 1097 | } 1098 | } // end decodeToBytes 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | /** 1105 | * Low-level access to decoding ASCII characters in 1106 | * the form of a byte array. Ignores GUNZIP option, if 1107 | * it's set. This is not generally a recommended method, 1108 | * although it is used internally as part of the decoding process. 1109 | * Special case: if len = 0, an empty array is returned. Still, 1110 | * if you need more speed and reduced memory footprint (and aren't 1111 | * gzipping), consider this method. 1112 | * 1113 | * @param source The Base64 encoded data 1114 | * @return decoded data 1115 | * @throws java.io.IOException java.io.IOException 1116 | * @since 2.3.1 1117 | */ 1118 | public static byte[] decode( byte[] source ) 1119 | throws java.io.IOException { 1120 | byte[] decoded = null; 1121 | // try { 1122 | decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); 1123 | // } catch( java.io.IOException ex ) { 1124 | // assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); 1125 | // } 1126 | return decoded; 1127 | } 1128 | 1129 | 1130 | 1131 | /** 1132 | * Low-level access to decoding ASCII characters in 1133 | * the form of a byte array. Ignores GUNZIP option, if 1134 | * it's set. This is not generally a recommended method, 1135 | * although it is used internally as part of the decoding process. 1136 | * Special case: if len = 0, an empty array is returned. Still, 1137 | * if you need more speed and reduced memory footprint (and aren't 1138 | * gzipping), consider this method. 1139 | * 1140 | * @param source The Base64 encoded data 1141 | * @param off The offset of where to begin decoding 1142 | * @param len The length of characters to decode 1143 | * @param options Can specify options such as alphabet type to use 1144 | * @return decoded data 1145 | * @throws java.io.IOException If bogus characters exist in source data 1146 | * @since 1.3 1147 | */ 1148 | public static byte[] decode( byte[] source, int off, int len, int options ) 1149 | throws java.io.IOException { 1150 | 1151 | // Lots of error checking and exception throwing 1152 | if( source == null ){ 1153 | throw new NullPointerException( "Cannot decode null source array." ); 1154 | } // end if 1155 | if( off < 0 || off + len > source.length ){ 1156 | throw new IllegalArgumentException( String.format( 1157 | "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); 1158 | } // end if 1159 | 1160 | if( len == 0 ){ 1161 | return new byte[0]; 1162 | }else if( len < 4 ){ 1163 | throw new IllegalArgumentException( 1164 | "Base64-encoded string must have at least four characters, but length specified was " + len ); 1165 | } // end if 1166 | 1167 | byte[] DECODABET = getDecodabet( options ); 1168 | 1169 | int len34 = len * 3 / 4; // Estimate on array size 1170 | byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output 1171 | int outBuffPosn = 0; // Keep track of where we're writing 1172 | 1173 | byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space 1174 | int b4Posn = 0; // Keep track of four byte input buffer 1175 | int i = 0; // Source array counter 1176 | byte sbiDecode = 0; // Special value from DECODABET 1177 | 1178 | for( i = off; i < off+len; i++ ) { // Loop through source 1179 | 1180 | sbiDecode = DECODABET[ source[i]&0xFF ]; 1181 | 1182 | // White space, Equals sign, or legit Base64 character 1183 | // Note the values such as -5 and -9 in the 1184 | // DECODABETs at the top of the file. 1185 | if( sbiDecode >= WHITE_SPACE_ENC ) { 1186 | if( sbiDecode >= EQUALS_SIGN_ENC ) { 1187 | b4[ b4Posn++ ] = source[i]; // Save non-whitespace 1188 | if( b4Posn > 3 ) { // Time to decode? 1189 | outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); 1190 | b4Posn = 0; 1191 | 1192 | // If that was the equals sign, break out of 'for' loop 1193 | if( source[i] == EQUALS_SIGN ) { 1194 | break; 1195 | } // end if: equals sign 1196 | } // end if: quartet built 1197 | } // end if: equals sign or better 1198 | } // end if: white space, equals sign or better 1199 | else { 1200 | // There's a bad input character in the Base64 stream. 1201 | throw new java.io.IOException( String.format( 1202 | "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); 1203 | } // end else: 1204 | } // each input character 1205 | 1206 | byte[] out = new byte[ outBuffPosn ]; 1207 | System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); 1208 | return out; 1209 | } // end decode 1210 | 1211 | 1212 | 1213 | 1214 | /** 1215 | * Decodes data from Base64 notation, automatically 1216 | * detecting gzip-compressed data and decompressing it. 1217 | * 1218 | * @param s the string to decode 1219 | * @return the decoded data 1220 | * @throws java.io.IOException If there is a problem 1221 | * @since 1.4 1222 | */ 1223 | public static byte[] decode( String s ) throws java.io.IOException { 1224 | return decode( s, NO_OPTIONS ); 1225 | } 1226 | 1227 | 1228 | 1229 | /** 1230 | * Decodes data from Base64 notation, automatically 1231 | * detecting gzip-compressed data and decompressing it. 1232 | * 1233 | * @param s the string to decode 1234 | * @param options encode options such as URL_SAFE 1235 | * @return the decoded data 1236 | * @throws java.io.IOException if there is an error 1237 | * @throws NullPointerException if s is null 1238 | * @since 1.4 1239 | */ 1240 | public static byte[] decode( String s, int options ) throws java.io.IOException { 1241 | 1242 | if( s == null ){ 1243 | throw new NullPointerException( "Input string was null." ); 1244 | } // end if 1245 | 1246 | byte[] bytes; 1247 | try { 1248 | bytes = s.getBytes( PREFERRED_ENCODING ); 1249 | } // end try 1250 | catch( java.io.UnsupportedEncodingException uee ) { 1251 | bytes = s.getBytes(); 1252 | } // end catch 1253 | // 1254 | 1255 | // Decode 1256 | bytes = decode( bytes, 0, bytes.length, options ); 1257 | 1258 | // Check to see if it's gzip-compressed 1259 | // GZIP Magic Two-Byte Number: 0x8b1f (35615) 1260 | boolean dontGunzip = (options & DONT_GUNZIP) != 0; 1261 | if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { 1262 | 1263 | int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); 1264 | if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { 1265 | java.io.ByteArrayInputStream bais = null; 1266 | java.util.zip.GZIPInputStream gzis = null; 1267 | java.io.ByteArrayOutputStream baos = null; 1268 | byte[] buffer = new byte[2048]; 1269 | int length = 0; 1270 | 1271 | try { 1272 | baos = new java.io.ByteArrayOutputStream(); 1273 | bais = new java.io.ByteArrayInputStream( bytes ); 1274 | gzis = new java.util.zip.GZIPInputStream( bais ); 1275 | 1276 | while( ( length = gzis.read( buffer ) ) >= 0 ) { 1277 | baos.write(buffer,0,length); 1278 | } // end while: reading input 1279 | 1280 | // No error? Get new bytes. 1281 | bytes = baos.toByteArray(); 1282 | 1283 | } // end try 1284 | catch( java.io.IOException e ) { 1285 | e.printStackTrace(); 1286 | // Just return originally-decoded bytes 1287 | } // end catch 1288 | finally { 1289 | try{ baos.close(); } catch( Exception e ){} 1290 | try{ gzis.close(); } catch( Exception e ){} 1291 | try{ bais.close(); } catch( Exception e ){} 1292 | } // end finally 1293 | 1294 | } // end if: gzipped 1295 | } // end if: bytes.length >= 2 1296 | 1297 | return bytes; 1298 | } // end decode 1299 | 1300 | 1301 | 1302 | /** 1303 | * Attempts to decode Base64 data and deserialize a Java 1304 | * Object within. Returns null if there was an error. 1305 | * 1306 | * @param encodedObject The Base64 data to decode 1307 | * @return The decoded and deserialized object 1308 | * @throws NullPointerException if encodedObject is null 1309 | * @throws java.io.IOException if there is a general error 1310 | * @throws ClassNotFoundException if the decoded object is of a 1311 | * class that cannot be found by the JVM 1312 | * @since 1.5 1313 | */ 1314 | public static Object decodeToObject( String encodedObject ) 1315 | throws java.io.IOException, ClassNotFoundException { 1316 | return decodeToObject(encodedObject,NO_OPTIONS,null); 1317 | } 1318 | 1319 | 1320 | /** 1321 | * Attempts to decode Base64 data and deserialize a Java 1322 | * Object within. Returns null if there was an error. 1323 | * If loader is not null, it will be the class loader 1324 | * used when deserializing. 1325 | * 1326 | * @param encodedObject The Base64 data to decode 1327 | * @param options Various parameters related to decoding 1328 | * @param loader Optional class loader to use in deserializing classes. 1329 | * @return The decoded and deserialized object 1330 | * @throws NullPointerException if encodedObject is null 1331 | * @throws java.io.IOException if there is a general error 1332 | * @throws ClassNotFoundException if the decoded object is of a 1333 | * class that cannot be found by the JVM 1334 | * @since 2.3.4 1335 | */ 1336 | public static Object decodeToObject( 1337 | String encodedObject, int options, final ClassLoader loader ) 1338 | throws java.io.IOException, ClassNotFoundException { 1339 | 1340 | // Decode and gunzip if necessary 1341 | byte[] objBytes = decode( encodedObject, options ); 1342 | 1343 | java.io.ByteArrayInputStream bais = null; 1344 | java.io.ObjectInputStream ois = null; 1345 | Object obj = null; 1346 | 1347 | try { 1348 | bais = new java.io.ByteArrayInputStream( objBytes ); 1349 | 1350 | // If no custom class loader is provided, use Java's builtin OIS. 1351 | if( loader == null ){ 1352 | ois = new java.io.ObjectInputStream( bais ); 1353 | } // end if: no loader provided 1354 | 1355 | // Else make a customized object input stream that uses 1356 | // the provided class loader. 1357 | else { 1358 | ois = new java.io.ObjectInputStream(bais){ 1359 | @Override 1360 | public Class> resolveClass(java.io.ObjectStreamClass streamClass) 1361 | throws java.io.IOException, ClassNotFoundException { 1362 | Class c = Class.forName(streamClass.getName(), false, loader); 1363 | if( c == null ){ 1364 | return super.resolveClass(streamClass); 1365 | } else { 1366 | return c; // Class loader knows of this class. 1367 | } // end else: not null 1368 | } // end resolveClass 1369 | }; // end ois 1370 | } // end else: no custom class loader 1371 | 1372 | obj = ois.readObject(); 1373 | } // end try 1374 | catch( java.io.IOException e ) { 1375 | throw e; // Catch and throw in order to execute finally{} 1376 | } // end catch 1377 | catch( ClassNotFoundException e ) { 1378 | throw e; // Catch and throw in order to execute finally{} 1379 | } // end catch 1380 | finally { 1381 | try{ bais.close(); } catch( Exception e ){} 1382 | try{ ois.close(); } catch( Exception e ){} 1383 | } // end finally 1384 | 1385 | return obj; 1386 | } // end decodeObject 1387 | 1388 | 1389 | 1390 | /** 1391 | * Convenience method for encoding data to a file. 1392 | * 1393 | *As of v 2.3, if there is a error, 1394 | * the method will throw an java.io.IOException. This is new to v2.3! 1395 | * In earlier versions, it just returned false, but 1396 | * in retrospect that's a pretty poor way to handle it.
1397 | * 1398 | * @param dataToEncode byte array of data to encode in base64 form 1399 | * @param filename Filename for saving encoded data 1400 | * @throws java.io.IOException if there is an error 1401 | * @throws NullPointerException if dataToEncode is null 1402 | * @since 2.1 1403 | */ 1404 | public static void encodeToFile( byte[] dataToEncode, String filename ) 1405 | throws java.io.IOException { 1406 | 1407 | if( dataToEncode == null ){ 1408 | throw new NullPointerException( "Data to encode was null." ); 1409 | } // end iff 1410 | 1411 | OutputStream bos = null; 1412 | try { 1413 | bos = new OutputStream( 1414 | new java.io.FileOutputStream( filename ), Base64.ENCODE ); 1415 | bos.write( dataToEncode ); 1416 | } // end try 1417 | catch( java.io.IOException e ) { 1418 | throw e; // Catch and throw to execute finally{} block 1419 | } // end catch: java.io.IOException 1420 | finally { 1421 | try{ bos.close(); } catch( Exception e ){} 1422 | } // end finally 1423 | 1424 | } // end encodeToFile 1425 | 1426 | 1427 | /** 1428 | * Convenience method for decoding data to a file. 1429 | * 1430 | *As of v 2.3, if there is a error, 1431 | * the method will throw an java.io.IOException. This is new to v2.3! 1432 | * In earlier versions, it just returned false, but 1433 | * in retrospect that's a pretty poor way to handle it.
1434 | * 1435 | * @param dataToDecode Base64-encoded data as a string 1436 | * @param filename Filename for saving decoded data 1437 | * @throws java.io.IOException if there is an error 1438 | * @since 2.1 1439 | */ 1440 | public static void decodeToFile( String dataToDecode, String filename ) 1441 | throws java.io.IOException { 1442 | 1443 | OutputStream bos = null; 1444 | try{ 1445 | bos = new OutputStream( 1446 | new java.io.FileOutputStream( filename ), Base64.DECODE ); 1447 | bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); 1448 | } // end try 1449 | catch( java.io.IOException e ) { 1450 | throw e; // Catch and throw to execute finally{} block 1451 | } // end catch: java.io.IOException 1452 | finally { 1453 | try{ bos.close(); } catch( Exception e ){} 1454 | } // end finally 1455 | 1456 | } // end decodeToFile 1457 | 1458 | 1459 | 1460 | 1461 | /** 1462 | * Convenience method for reading a base64-encoded 1463 | * file and decoding it. 1464 | * 1465 | *As of v 2.3, if there is a error, 1466 | * the method will throw an java.io.IOException. This is new to v2.3! 1467 | * In earlier versions, it just returned false, but 1468 | * in retrospect that's a pretty poor way to handle it.
1469 | * 1470 | * @param filename Filename for reading encoded data 1471 | * @return decoded byte array 1472 | * @throws java.io.IOException if there is an error 1473 | * @since 2.1 1474 | */ 1475 | public static byte[] decodeFromFile( String filename ) 1476 | throws java.io.IOException { 1477 | 1478 | byte[] decodedData = null; 1479 | InputStream bis = null; 1480 | try 1481 | { 1482 | // Set up some useful variables 1483 | java.io.File file = new java.io.File( filename ); 1484 | byte[] buffer = null; 1485 | int length = 0; 1486 | int numBytes = 0; 1487 | 1488 | // Check for size of file 1489 | if( file.length() > Integer.MAX_VALUE ) 1490 | { 1491 | throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); 1492 | } // end if: file too big for int index 1493 | buffer = new byte[ (int)file.length() ]; 1494 | 1495 | // Open a stream 1496 | bis = new InputStream( 1497 | new java.io.BufferedInputStream( 1498 | new java.io.FileInputStream( file ) ), Base64.DECODE ); 1499 | 1500 | // Read until done 1501 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { 1502 | length += numBytes; 1503 | } // end while 1504 | 1505 | // Save in a variable to return 1506 | decodedData = new byte[ length ]; 1507 | System.arraycopy( buffer, 0, decodedData, 0, length ); 1508 | 1509 | } // end try 1510 | catch( java.io.IOException e ) { 1511 | throw e; // Catch and release to execute finally{} 1512 | } // end catch: java.io.IOException 1513 | finally { 1514 | try{ bis.close(); } catch( Exception e) {} 1515 | } // end finally 1516 | 1517 | return decodedData; 1518 | } // end decodeFromFile 1519 | 1520 | 1521 | 1522 | /** 1523 | * Convenience method for reading a binary file 1524 | * and base64-encoding it. 1525 | * 1526 | *As of v 2.3, if there is a error, 1527 | * the method will throw an java.io.IOException. This is new to v2.3! 1528 | * In earlier versions, it just returned false, but 1529 | * in retrospect that's a pretty poor way to handle it.
1530 | * 1531 | * @param filename Filename for reading binary data 1532 | * @return base64-encoded string 1533 | * @throws java.io.IOException if there is an error 1534 | * @since 2.1 1535 | */ 1536 | public static String encodeFromFile( String filename ) 1537 | throws java.io.IOException { 1538 | 1539 | String encodedData = null; 1540 | InputStream bis = null; 1541 | try 1542 | { 1543 | // Set up some useful variables 1544 | java.io.File file = new java.io.File( filename ); 1545 | byte[] buffer = new byte[ java.lang.Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) 1546 | int length = 0; 1547 | int numBytes = 0; 1548 | 1549 | // Open a stream 1550 | bis = new InputStream( 1551 | new java.io.BufferedInputStream( 1552 | new java.io.FileInputStream( file ) ), Base64.ENCODE ); 1553 | 1554 | // Read until done 1555 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { 1556 | length += numBytes; 1557 | } // end while 1558 | 1559 | // Save in a variable to return 1560 | encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); 1561 | 1562 | } // end try 1563 | catch( java.io.IOException e ) { 1564 | throw e; // Catch and release to execute finally{} 1565 | } // end catch: java.io.IOException 1566 | finally { 1567 | try{ bis.close(); } catch( Exception e) {} 1568 | } // end finally 1569 | 1570 | return encodedData; 1571 | } // end encodeFromFile 1572 | 1573 | /** 1574 | * Reads infile and encodes it to outfile. 1575 | * 1576 | * @param infile Input file 1577 | * @param outfile Output file 1578 | * @throws java.io.IOException if there is an error 1579 | * @since 2.2 1580 | */ 1581 | public static void encodeFileToFile( String infile, String outfile ) 1582 | throws java.io.IOException { 1583 | 1584 | String encoded = Base64.encodeFromFile( infile ); 1585 | java.io.OutputStream out = null; 1586 | try{ 1587 | out = new java.io.BufferedOutputStream( 1588 | new java.io.FileOutputStream( outfile ) ); 1589 | out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. 1590 | } // end try 1591 | catch( java.io.IOException e ) { 1592 | throw e; // Catch and release to execute finally{} 1593 | } // end catch 1594 | finally { 1595 | try { out.close(); } 1596 | catch( Exception ex ){} 1597 | } // end finally 1598 | } // end encodeFileToFile 1599 | 1600 | 1601 | /** 1602 | * Reads infile and decodes it to outfile. 1603 | * 1604 | * @param infile Input file 1605 | * @param outfile Output file 1606 | * @throws java.io.IOException if there is an error 1607 | * @since 2.2 1608 | */ 1609 | public static void decodeFileToFile( String infile, String outfile ) 1610 | throws java.io.IOException { 1611 | 1612 | byte[] decoded = Base64.decodeFromFile( infile ); 1613 | java.io.OutputStream out = null; 1614 | try{ 1615 | out = new java.io.BufferedOutputStream( 1616 | new java.io.FileOutputStream( outfile ) ); 1617 | out.write( decoded ); 1618 | } // end try 1619 | catch( java.io.IOException e ) { 1620 | throw e; // Catch and release to execute finally{} 1621 | } // end catch 1622 | finally { 1623 | try { out.close(); } 1624 | catch( Exception ex ){} 1625 | } // end finally 1626 | } // end decodeFileToFile 1627 | 1628 | 1629 | /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ 1630 | 1631 | 1632 | 1633 | /** 1634 | * A {@link InputStream} will read data from another 1635 | * java.io.InputStream, given in the constructor, 1636 | * and encode/decode to/from Base64 notation on the fly. 1637 | * 1638 | * @see Base64 1639 | * @since 1.3 1640 | */ 1641 | public static class InputStream extends java.io.FilterInputStream { 1642 | 1643 | private boolean encode; // Encoding or decoding 1644 | private int position; // Current position in the buffer 1645 | private byte[] buffer; // Small buffer holding converted data 1646 | private int bufferLength; // Length of buffer (3 or 4) 1647 | private int numSigBytes; // Number of meaningful bytes in the buffer 1648 | private int lineLength; 1649 | private boolean breakLines; // Break lines at less than 80 characters 1650 | private int options; // Record options used to create the stream. 1651 | private byte[] decodabet; // Local copies to avoid extra method calls 1652 | 1653 | 1654 | /** 1655 | * Constructs a {@link InputStream} in DECODE mode. 1656 | * 1657 | * @param in the java.io.InputStream from which to read data. 1658 | * @since 1.3 1659 | */ 1660 | public InputStream( java.io.InputStream in ) { 1661 | this( in, DECODE ); 1662 | } // end constructor 1663 | 1664 | 1665 | /** 1666 | * Constructs a {@link InputStream} in 1667 | * either ENCODE or DECODE mode. 1668 | *1669 | * Valid options:
1670 | * ENCODE or DECODE: Encode or Decode as data is read. 1671 | * DO_BREAK_LINES: break lines at 76 characters 1672 | * (only meaningful when encoding) 1673 | *1674 | *
1675 | * Example: new Base64.InputStream( in, Base64.DECODE )
1676 | *
1677 | *
1678 | * @param in the java.io.InputStream from which to read data.
1679 | * @param options Specified options
1680 | * @see Base64#ENCODE
1681 | * @see Base64#DECODE
1682 | * @see Base64#DO_BREAK_LINES
1683 | * @since 2.0
1684 | */
1685 | public InputStream( java.io.InputStream in, int options ) {
1686 |
1687 | super( in );
1688 | this.options = options; // Record for later
1689 | this.breakLines = (options & DO_BREAK_LINES) > 0;
1690 | this.encode = (options & ENCODE) > 0;
1691 | this.bufferLength = encode ? 4 : 3;
1692 | this.buffer = new byte[ bufferLength ];
1693 | this.position = -1;
1694 | this.lineLength = 0;
1695 | this.decodabet = getDecodabet(options);
1696 | } // end constructor
1697 |
1698 | /**
1699 | * Reads enough of the input stream to convert
1700 | * to/from Base64 and returns the next byte.
1701 | *
1702 | * @return next byte
1703 | * @since 1.3
1704 | */
1705 | @Override
1706 | public int read() throws java.io.IOException {
1707 |
1708 | // Do we need to get data?
1709 | if( position < 0 ) {
1710 | if( encode ) {
1711 | byte[] b3 = new byte[3];
1712 | int numBinaryBytes = 0;
1713 | for( int i = 0; i < 3; i++ ) {
1714 | int b = in.read();
1715 |
1716 | // If end of stream, b is -1.
1717 | if( b >= 0 ) {
1718 | b3[i] = (byte)b;
1719 | numBinaryBytes++;
1720 | } else {
1721 | break; // out of for loop
1722 | } // end else: end of stream
1723 |
1724 | } // end for: each needed input byte
1725 |
1726 | if( numBinaryBytes > 0 ) {
1727 | encode3to4( b3, 0, numBinaryBytes, buffer, 0, options );
1728 | position = 0;
1729 | numSigBytes = 4;
1730 | } // end if: got data
1731 | else {
1732 | return -1; // Must be end of stream
1733 | } // end else
1734 | } // end if: encoding
1735 |
1736 | // Else decoding
1737 | else {
1738 | byte[] b4 = new byte[4];
1739 | int i = 0;
1740 | for( i = 0; i < 4; i++ ) {
1741 | // Read four "meaningful" bytes:
1742 | int b = 0;
1743 | do{ b = in.read(); }
1744 | while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC );
1745 |
1746 | if( b < 0 ) {
1747 | break; // Reads a -1 if end of stream
1748 | } // end if: end of stream
1749 |
1750 | b4[i] = (byte)b;
1751 | } // end for: each needed input byte
1752 |
1753 | if( i == 4 ) {
1754 | numSigBytes = decode4to3( b4, 0, buffer, 0, options );
1755 | position = 0;
1756 | } // end if: got four characters
1757 | else if( i == 0 ){
1758 | return -1;
1759 | } // end else if: also padded correctly
1760 | else {
1761 | // Must have broken out from above.
1762 | throw new java.io.IOException( "Improperly padded Base64 input." );
1763 | } // end
1764 |
1765 | } // end else: decode
1766 | } // end else: get data
1767 |
1768 | // Got data?
1769 | if( position >= 0 ) {
1770 | // End of relevant data?
1771 | if( /*!encode &&*/ position >= numSigBytes ){
1772 | return -1;
1773 | } // end if: got data
1774 |
1775 | if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) {
1776 | lineLength = 0;
1777 | return '\n';
1778 | } // end if
1779 | else {
1780 | lineLength++; // This isn't important when decoding
1781 | // but throwing an extra "if" seems
1782 | // just as wasteful.
1783 |
1784 | int b = buffer[ position++ ];
1785 |
1786 | if( position >= bufferLength ) {
1787 | position = -1;
1788 | } // end if: end
1789 |
1790 | return b & 0xFF; // This is how you "cast" a byte that's
1791 | // intended to be unsigned.
1792 | } // end else
1793 | } // end if: position >= 0
1794 |
1795 | // Else error
1796 | else {
1797 | throw new java.io.IOException( "Error in Base64 code reading stream." );
1798 | } // end else
1799 | } // end read
1800 |
1801 |
1802 | /**
1803 | * Calls {@link #read()} repeatedly until the end of stream
1804 | * is reached or len bytes are read.
1805 | * Returns number of bytes read into array or -1 if
1806 | * end of stream is encountered.
1807 | *
1808 | * @param dest array to hold values
1809 | * @param off offset for array
1810 | * @param len max number of bytes to read into array
1811 | * @return bytes read into array or -1 if end of stream is encountered.
1812 | * @since 1.3
1813 | */
1814 | @Override
1815 | public int read( byte[] dest, int off, int len )
1816 | throws java.io.IOException {
1817 | int i;
1818 | int b;
1819 | for( i = 0; i < len; i++ ) {
1820 | b = read();
1821 |
1822 | if( b >= 0 ) {
1823 | dest[off + i] = (byte) b;
1824 | }
1825 | else if( i == 0 ) {
1826 | return -1;
1827 | }
1828 | else {
1829 | break; // Out of 'for' loop
1830 | } // Out of 'for' loop
1831 | } // end for: each byte read
1832 | return i;
1833 | } // end read
1834 |
1835 | } // end inner class InputStream
1836 |
1837 |
1838 |
1839 |
1840 |
1841 |
1842 | /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
1843 |
1844 |
1845 |
1846 | /**
1847 | * A {@link OutputStream} will write data to another
1848 | * java.io.OutputStream, given in the constructor,
1849 | * and encode/decode to/from Base64 notation on the fly.
1850 | *
1851 | * @see Base64
1852 | * @since 1.3
1853 | */
1854 | public static class OutputStream extends java.io.FilterOutputStream {
1855 |
1856 | private boolean encode;
1857 | private int position;
1858 | private byte[] buffer;
1859 | private int bufferLength;
1860 | private int lineLength;
1861 | private boolean breakLines;
1862 | private byte[] b4; // Scratch used in a few places
1863 | private boolean suspendEncoding;
1864 | private int options; // Record for later
1865 | private byte[] decodabet; // Local copies to avoid extra method calls
1866 |
1867 | /**
1868 | * Constructs a {@link OutputStream} in ENCODE mode.
1869 | *
1870 | * @param out the java.io.OutputStream to which data will be written.
1871 | * @since 1.3
1872 | */
1873 | public OutputStream( java.io.OutputStream out ) {
1874 | this( out, ENCODE );
1875 | } // end constructor
1876 |
1877 |
1878 | /**
1879 | * Constructs a {@link OutputStream} in
1880 | * either ENCODE or DECODE mode.
1881 | *
1882 | * Valid options:
1883 | * ENCODE or DECODE: Encode or Decode as data is read. 1884 | * DO_BREAK_LINES: don't break lines at 76 characters 1885 | * (only meaningful when encoding) 1886 | *1887 | *
1888 | * Example: new Base64.OutputStream( out, Base64.ENCODE )
1889 | *
1890 | * @param out the java.io.OutputStream to which data will be written.
1891 | * @param options Specified options.
1892 | * @see Base64#ENCODE
1893 | * @see Base64#DECODE
1894 | * @see Base64#DO_BREAK_LINES
1895 | * @since 1.3
1896 | */
1897 | public OutputStream( java.io.OutputStream out, int options ) {
1898 | super( out );
1899 | this.breakLines = (options & DO_BREAK_LINES) != 0;
1900 | this.encode = (options & ENCODE) != 0;
1901 | this.bufferLength = encode ? 3 : 4;
1902 | this.buffer = new byte[ bufferLength ];
1903 | this.position = 0;
1904 | this.lineLength = 0;
1905 | this.suspendEncoding = false;
1906 | this.b4 = new byte[4];
1907 | this.options = options;
1908 | this.decodabet = getDecodabet(options);
1909 | } // end constructor
1910 |
1911 |
1912 | /**
1913 | * Writes the byte to the output stream after
1914 | * converting to/from Base64 notation.
1915 | * When encoding, bytes are buffered three
1916 | * at a time before the output stream actually
1917 | * gets a write() call.
1918 | * When decoding, bytes are buffered four
1919 | * at a time.
1920 | *
1921 | * @param theByte the byte to write
1922 | * @since 1.3
1923 | */
1924 | @Override
1925 | public void write(int theByte)
1926 | throws java.io.IOException {
1927 | // Encoding suspended?
1928 | if( suspendEncoding ) {
1929 | this.out.write( theByte );
1930 | return;
1931 | } // end if: supsended
1932 |
1933 | // Encode?
1934 | if( encode ) {
1935 | buffer[ position++ ] = (byte)theByte;
1936 | if( position >= bufferLength ) { // Enough to encode.
1937 |
1938 | this.out.write( encode3to4( b4, buffer, bufferLength, options ) );
1939 |
1940 | lineLength += 4;
1941 | if( breakLines && lineLength >= MAX_LINE_LENGTH ) {
1942 | this.out.write( NEW_LINE );
1943 | lineLength = 0;
1944 | } // end if: end of line
1945 |
1946 | position = 0;
1947 | } // end if: enough to output
1948 | } // end if: encoding
1949 |
1950 | // Else, Decoding
1951 | else {
1952 | // Meaningful Base64 character?
1953 | if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) {
1954 | buffer[ position++ ] = (byte)theByte;
1955 | if( position >= bufferLength ) { // Enough to output.
1956 |
1957 | int len = Base64.decode4to3( buffer, 0, b4, 0, options );
1958 | out.write( b4, 0, len );
1959 | position = 0;
1960 | } // end if: enough to output
1961 | } // end if: meaningful base64 character
1962 | else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) {
1963 | throw new java.io.IOException( "Invalid character in Base64 data." );
1964 | } // end else: not white space either
1965 | } // end else: decoding
1966 | } // end write
1967 |
1968 |
1969 |
1970 | /**
1971 | * Calls {@link #write(int)} repeatedly until len
1972 | * bytes are written.
1973 | *
1974 | * @param theBytes array from which to read bytes
1975 | * @param off offset for array
1976 | * @param len max number of bytes to read into array
1977 | * @since 1.3
1978 | */
1979 | @Override
1980 | public void write( byte[] theBytes, int off, int len )
1981 | throws java.io.IOException {
1982 | // Encoding suspended?
1983 | if( suspendEncoding ) {
1984 | this.out.write( theBytes, off, len );
1985 | return;
1986 | } // end if: supsended
1987 |
1988 | for( int i = 0; i < len; i++ ) {
1989 | write( theBytes[ off + i ] );
1990 | } // end for: each byte written
1991 |
1992 | } // end write
1993 |
1994 |
1995 |
1996 | /**
1997 | * Method added by PHIL. [Thanks, PHIL. -Rob]
1998 | * This pads the buffer without closing the stream.
1999 | * @throws java.io.IOException if there's an error.
2000 | */
2001 | public void flushBase64() throws java.io.IOException {
2002 | if( position > 0 ) {
2003 | if( encode ) {
2004 | out.write( encode3to4( b4, buffer, position, options ) );
2005 | position = 0;
2006 | } // end if: encoding
2007 | else {
2008 | throw new java.io.IOException( "Base64 input not properly padded." );
2009 | } // end else: decoding
2010 | } // end if: buffer partially full
2011 |
2012 | } // end flush
2013 |
2014 |
2015 | /**
2016 | * Flushes and closes (I think, in the superclass) the stream.
2017 | *
2018 | * @since 1.3
2019 | */
2020 | @Override
2021 | public void close() throws java.io.IOException {
2022 | // 1. Ensure that pending characters are written
2023 | flushBase64();
2024 |
2025 | // 2. Actually close the stream
2026 | // Base class both flushes and closes.
2027 | super.close();
2028 |
2029 | buffer = null;
2030 | out = null;
2031 | } // end close
2032 |
2033 |
2034 |
2035 | /**
2036 | * Suspends encoding of the stream.
2037 | * May be helpful if you need to embed a piece of
2038 | * base64-encoded data in a stream.
2039 | *
2040 | * @throws java.io.IOException if there's an error flushing
2041 | * @since 1.5.1
2042 | */
2043 | public void suspendEncoding() throws java.io.IOException {
2044 | flushBase64();
2045 | this.suspendEncoding = true;
2046 | } // end suspendEncoding
2047 |
2048 |
2049 | /**
2050 | * Resumes encoding of the stream.
2051 | * May be helpful if you need to embed a piece of
2052 | * base64-encoded data in a stream.
2053 | *
2054 | * @since 1.5.1
2055 | */
2056 | public void resumeEncoding() {
2057 | this.suspendEncoding = false;
2058 | } // end resumeEncoding
2059 |
2060 |
2061 |
2062 | } // end inner class OutputStream
2063 |
2064 |
2065 | } // end class Base64
2066 |
--------------------------------------------------------------------------------
/src/main/java/com/starkbank/ellipticcurve/utils/BinaryAscii.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve.utils;
2 | import java.math.BigInteger;
3 | import java.util.Arrays;
4 |
5 |
6 | public final class BinaryAscii {
7 |
8 | /**
9 | *
10 | * @param string byteString
11 | * @return String
12 | */
13 | public static String hexFromBinary(ByteString string) {
14 | return hexFromBinary(string.getBytes());
15 | }
16 |
17 | /**
18 | *
19 | * @param bytes byte[]
20 | * @return String
21 | */
22 | public static String hexFromBinary(byte[] bytes) {
23 | StringBuilder hexString = new StringBuilder();
24 |
25 | for (byte aByte : bytes) {
26 | String hex = Integer.toHexString(0xFF & aByte);
27 | if (hex.length() == 1) {
28 | hexString.append('0');
29 | }
30 | hexString.append(hex);
31 | }
32 | return hexString.toString();
33 | }
34 |
35 | /**
36 | *
37 | * @param string string
38 | * @return byte[]
39 | */
40 | public static byte[] binaryFromHex(String string) {
41 | byte[] bytes = new BigInteger(string, 16).toByteArray();
42 | int i = 0;
43 | while (i < bytes.length && bytes[i] == 0) {
44 | i++;
45 | }
46 | return Arrays.copyOfRange(bytes, i, bytes.length);
47 | }
48 |
49 | /**
50 | *
51 | * @param c c
52 | * @return byte[]
53 | */
54 | public static byte[] toBytes(int c) {
55 | return new byte[]{(byte) c};
56 | }
57 |
58 | /**
59 | * Get a number representation of a string
60 | *
61 | * @param string String to be converted in a number
62 | * @return Number in hex from string
63 | */
64 | public static BigInteger numberFromString(byte[] string) {
65 | return new BigInteger(BinaryAscii.hexFromBinary(string), 16);
66 | }
67 |
68 | /**
69 | * Get a string representation of a number
70 | *
71 | * @param number number to be converted in a string
72 | * @param length length max number of character for the string
73 | * @return hexadecimal string
74 | */
75 | public static ByteString stringFromNumber(BigInteger number, int length) {
76 | String fmtStr = "%0" + String.valueOf(2 * length) + "x";
77 | String hexString = String.format(fmtStr, number);
78 | return new ByteString(BinaryAscii.binaryFromHex(hexString));
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/starkbank/ellipticcurve/utils/ByteString.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve.utils;
2 | import java.io.UnsupportedEncodingException;
3 | import java.util.Arrays;
4 |
5 |
6 | public class ByteString {
7 | private byte[] bytes;
8 |
9 | /**
10 | *
11 | */
12 | public ByteString() {
13 | bytes = new byte[]{};
14 | }
15 |
16 | /**
17 | *
18 | * @param bytes byte[]
19 | */
20 | public ByteString(byte[] bytes) {
21 | this.bytes = bytes;
22 | }
23 |
24 | /**
25 | *
26 | * @param index index
27 | * @return short
28 | */
29 | public short getShort(int index) {
30 | return (short) (bytes[index] & 0xFF);
31 | }
32 |
33 | /**
34 | *
35 | * @param start start
36 | * @return ByteString
37 | */
38 | public ByteString substring(int start) {
39 | return substring(start, bytes.length);
40 | }
41 |
42 | /**
43 | *
44 | * @param start start
45 | * @param end end
46 | * @return ByteString
47 | */
48 | public ByteString substring(int start, int end) {
49 | if (end > bytes.length) {
50 | end = bytes.length;
51 | }
52 | if (end < 0) {
53 | end = bytes.length - end;
54 | }
55 | if (start > end) {
56 | return new ByteString();
57 | }
58 |
59 | return new ByteString(Arrays.copyOfRange(bytes, start, end));
60 | }
61 |
62 | /**
63 | *
64 | * @return byte[]
65 | */
66 | public byte[] getBytes() {
67 | return Arrays.copyOf(bytes, bytes.length);
68 | }
69 |
70 | /**
71 | *
72 | * @return int
73 | */
74 | public int length() {
75 | return bytes.length;
76 | }
77 |
78 | /**
79 | *
80 | * @return boolean
81 | */
82 | public boolean isEmpty() {
83 | return bytes.length == 0;
84 | }
85 |
86 | /**
87 | *
88 | * @param b b
89 | */
90 | public void insert(byte[] b) {
91 | this.insert(bytes.length, b);
92 | }
93 |
94 | /**
95 | *
96 | * @param index index
97 | * @param b b
98 | */
99 | public void insert(int index, byte[] b) {
100 | byte[] result = new byte[b.length + bytes.length];
101 | System.arraycopy(bytes, 0, result, 0, index);
102 | System.arraycopy(b, 0, result, index, b.length);
103 | if (index < bytes.length) {
104 | System.arraycopy(bytes, index, result, b.length + index, bytes.length - index);
105 | }
106 | this.bytes = result;
107 | }
108 |
109 | /**
110 | *
111 | * @param index index
112 | * @param value value
113 | */
114 | public void replace(int index, byte value) {
115 | bytes[index] = value;
116 | }
117 |
118 | /**
119 | *
120 | * @return string
121 | */
122 | @Override
123 | public String toString() {
124 | if (bytes.length == 0) {
125 | return "";
126 | }
127 | try {
128 | return new String(bytes, "ASCII");
129 | } catch (UnsupportedEncodingException e) {
130 | throw new RuntimeException();
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/main/java/com/starkbank/ellipticcurve/utils/Der.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve.utils;
2 |
3 | import java.io.IOException;
4 | import java.math.BigInteger;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import static com.starkbank.ellipticcurve.utils.BinaryAscii.*;
9 |
10 |
11 | public class Der {
12 |
13 | private Der() {
14 | throw new UnsupportedOperationException("Der is a utility class and cannot be instantiated");
15 | }
16 |
17 | /**
18 | *
19 | * @param encodedPieces encodedPieces
20 | * @return ByteString
21 | */
22 | public static ByteString encodeSequence(ByteString... encodedPieces) {
23 | int totalLen = 0;
24 | ByteString stringPieces = new ByteString(toBytes(0x30));
25 | for (ByteString p : encodedPieces) {
26 | totalLen += p.length();
27 | stringPieces.insert(p.getBytes());
28 | }
29 | stringPieces.insert(1, encodeLength(totalLen).getBytes());
30 | return stringPieces;
31 | }
32 |
33 | /**
34 | *
35 | * @param length length
36 | * @return ByteString
37 | */
38 | public static ByteString encodeLength(int length) {
39 | assert length >= 0;
40 | if (length < 0x80) {
41 | return new ByteString(toBytes(length));
42 | }
43 | String hexString = String.format("%x", length);
44 | if (hexString.length() % 2 != 0) {
45 | hexString = "0" + hexString;
46 | }
47 | ByteString s = new ByteString(binaryFromHex(hexString));
48 | s.insert(0, toBytes((0x80 | s.length())));
49 | return s;
50 |
51 | }
52 |
53 | /**
54 | *
55 | * @param r r
56 | * @return ByteString
57 | */
58 | public static ByteString encodeInteger(BigInteger r) {
59 | assert r.compareTo(BigInteger.ZERO) >= 0;
60 | String h = String.format("%x", r);
61 | if (h.length() % 2 != 0) {
62 | h = "0" + h;
63 | }
64 | ByteString s = new ByteString(binaryFromHex(h));
65 | short num = s.getShort(0);
66 | if (num <= 0x7F) {
67 | s.insert(0, toBytes(s.length()));
68 | s.insert(0, toBytes(0x02));
69 | return s;
70 | }
71 | int length = s.length();
72 | s.insert(0, toBytes(0x00));
73 | s.insert(0, toBytes((length + 1)));
74 | s.insert(0, toBytes(0x02));
75 | return s;
76 | }
77 |
78 | /**
79 | *
80 | * @param n n
81 | * @return ByteString
82 | */
83 | public static ByteString encodeNumber(long n) {
84 | ByteString b128Digits = new ByteString();
85 | while (n != 0) {
86 | b128Digits.insert(0, toBytes((int) (n & 0x7f) | 0x80));
87 | n = n >> 7;
88 | }
89 | if (b128Digits.isEmpty()) {
90 | b128Digits.insert(toBytes(0));
91 | }
92 | int lastIndex = b128Digits.length() - 1;
93 | b128Digits.replace(lastIndex, (byte) (b128Digits.getShort(lastIndex) & 0x7f));
94 | return b128Digits;
95 | }
96 |
97 | /**
98 | *
99 | * @param pieces pieces
100 | * @return ByteString
101 | */
102 | public static ByteString encodeOid(long... pieces) {
103 | long first = pieces[0];
104 | long second = pieces[1];
105 | assert first <= 2;
106 | assert second <= 39;
107 | ByteString body = new ByteString();
108 | for (int i = 2; i < pieces.length; i++) {
109 | body.insert(encodeNumber(pieces[i]).getBytes());
110 | }
111 | body.insert(0, toBytes((int) (40 * first + second)));
112 | body.insert(0, encodeLength(body.length()).getBytes());
113 | body.insert(0, toBytes(0x06));
114 | return body;
115 | }
116 |
117 | /**
118 | *
119 | * @param s s
120 | * @return ByteString
121 | */
122 | public static ByteString encodeBitString(ByteString s) {
123 | s.insert(0, encodeLength(s.length()).getBytes());
124 | s.insert(0, toBytes(0x03));
125 | return s;
126 | }
127 |
128 | /**
129 | *
130 | * @param s s
131 | * @return ByteString
132 | */
133 | public static ByteString encodeOctetString(ByteString s) {
134 | s.insert(0, encodeLength(s.length()).getBytes());
135 | s.insert(0, toBytes(0x04));
136 | return s;
137 | }
138 |
139 | /**
140 | *
141 | * @param tag tag
142 | * @param value value
143 | * @return ByteString
144 | */
145 | public static ByteString encodeConstructed(long tag, ByteString value) {
146 | value.insert(0, encodeLength(value.length()).getBytes());
147 | value.insert(0, toBytes((int) (0xa0 + tag)));
148 | return value;
149 | }
150 |
151 | /**
152 | *
153 | * @param string string
154 | * @return int[]
155 | */
156 | public static int[] readLength(ByteString string) {
157 | short num = string.getShort(0);
158 | if ((num & 0x80) == 0) {
159 | return new int[]{num & 0x7f, 1};
160 | }
161 |
162 | int llen = num & 0x7f;
163 | if (llen > string.length() - 1) {
164 | throw new RuntimeException("ran out of length bytes");
165 | }
166 | return new int[]{Integer.valueOf(hexFromBinary(string.substring(1, 1 + llen)), 16), 1 + llen};
167 | }
168 |
169 | /**
170 | *
171 | * @param string string
172 | * @return int[]
173 | */
174 | public static int[] readNumber(ByteString string) {
175 | int number = 0;
176 | int llen = 0;
177 | for (; ; ) {
178 | if (llen > string.length()) {
179 | throw new RuntimeException("ran out of length bytes");
180 | }
181 | number = number << 7;
182 | short d = string.getShort(llen);
183 | number += (d & 0x7f);
184 | llen += 1;
185 | if ((d & 0x80) == 0)
186 | break;
187 | }
188 | return new int[]{number, llen};
189 | }
190 |
191 | /**
192 | *
193 | * @param string string
194 | * @return ByteString[]
195 | */
196 | public static ByteString[] removeSequence(ByteString string) {
197 | short n = string.getShort(0);
198 | if (n != 0x30) {
199 | throw new RuntimeException(String.format("wanted sequence (0x30), got 0x%02x", n));
200 | }
201 | int[] l = readLength(string.substring(1));
202 | long endseq = 1 + l[0] + l[1];
203 | return new ByteString[]{string.substring(1 + l[1], (int) endseq), string.substring((int) endseq)};
204 | }
205 |
206 | /**
207 | *
208 | * @param string string
209 | * @return Object[]
210 | */
211 | public static Object[] removeInteger(ByteString string) {
212 | short n = string.getShort(0);
213 | if (n != 0x02) {
214 | throw new RuntimeException(String.format("wanted integer (0x02), got 0x%02x", n));
215 | }
216 | int[] l = readLength(string.substring(1));
217 | int length = l[0];
218 | int llen = l[1];
219 | ByteString numberbytes = string.substring(1 + llen, 1 + llen + length);
220 | ByteString rest = string.substring(1 + llen + length);
221 | short nbytes = numberbytes.getShort(0);
222 | assert nbytes < 0x80;
223 | return new Object[]{new BigInteger(hexFromBinary(numberbytes), 16), rest};
224 | }
225 |
226 | /**
227 | *
228 | * @param string string
229 | * @return Object[]
230 | */
231 | public static Object[] removeObject(ByteString string) {
232 | int n = string.getShort(0);
233 | if (n != 0x06) {
234 | throw new RuntimeException(String.format("wanted object (0x06), got 0x%02x", n));
235 | }
236 | int[] l = readLength(string.substring(1));
237 | int length = l[0];
238 | int lengthlength = l[1];
239 | ByteString body = string.substring(1 + lengthlength, 1 + lengthlength + length);
240 | ByteString rest = string.substring(1 + lengthlength + length);
241 | List numbers = new ArrayList();
242 | while (!body.isEmpty()) {
243 | l = readNumber(body);
244 | n = l[0];
245 | int ll = l[1];
246 | numbers.add(n);
247 | body = body.substring(ll);
248 | }
249 | long n0 = Integer.valueOf(numbers.remove(0).toString());
250 | long first = n0 / 40;
251 | long second = n0 - (40 * first);
252 | numbers.add(0, first);
253 | numbers.add(1, second);
254 | long[] numbersArray = new long[numbers.size()];
255 | for (int i = 0; i < numbers.size(); i++) {
256 | numbersArray[i] = Long.valueOf(numbers.get(i).toString());
257 | }
258 | return new Object[]{numbersArray, rest};
259 | }
260 |
261 | /**
262 | *
263 | * @param string string
264 | * @return ByteString
265 | */
266 | public static ByteString[] removeBitString(ByteString string) {
267 | short n = string.getShort(0);
268 | if (n != 0x03) {
269 | throw new RuntimeException(String.format("wanted bitstring (0x03), got 0x%02x", n));
270 | }
271 | int[] l = readLength(string.substring(1));
272 | int length = l[0];
273 | int llen = l[1];
274 | ByteString body = string.substring(1 + llen, 1 + llen + length);
275 | ByteString rest = string.substring(1 + llen + length);
276 | return new ByteString[]{body, rest};
277 | }
278 |
279 | /**
280 | *
281 | * @param string string
282 | * @return ByteString[]
283 | */
284 | public static ByteString[] removeOctetString(ByteString string) {
285 | short n = string.getShort(0);
286 | if (n != 0x04) {
287 | throw new RuntimeException(String.format("wanted octetstring (0x04), got 0x%02x", n));
288 | }
289 | int[] l = readLength(string.substring(1));
290 | int length = l[0];
291 | int llen = l[1];
292 | ByteString body = string.substring(1 + llen, 1 + llen + length);
293 | ByteString rest = string.substring(1 + llen + length);
294 | return new ByteString[]{body, rest};
295 | }
296 |
297 | /**
298 | *
299 | * @param string string
300 | * @return Object[]
301 | */
302 | public static Object[] removeConstructed(ByteString string) {
303 | short s0 = string.getShort(0);
304 | if ((s0 & 0xe0) != 0xa0) {
305 | throw new RuntimeException(String.format("wanted constructed tag (0xa0-0xbf), got 0x%02x", s0));
306 | }
307 | int tag = s0 & 0x1f;
308 | int[] l = readLength(string.substring(1));
309 | int length = l[0];
310 | int llen = l[1];
311 | ByteString body = string.substring(1 + llen, 1 + llen + length);
312 | ByteString rest = string.substring(1 + llen + length);
313 | return new Object[]{tag, body, rest};
314 | }
315 |
316 | /**
317 | *
318 | * @param pem pem
319 | * @return ByteString
320 | */
321 | public static ByteString fromPem(String pem) {
322 | String[] pieces = pem.split("\n");
323 | StringBuilder d = new StringBuilder();
324 | for (String p : pieces) {
325 | if (!p.isEmpty() && !p.startsWith("-----")) {
326 | d.append(p.trim());
327 | }
328 | }
329 | try {
330 | return new ByteString(Base64.decode(d.toString()));
331 | } catch (IOException e) {
332 | throw new IllegalArgumentException("Corrupted pem string! Could not decode base64 from it");
333 | }
334 | }
335 |
336 | /**
337 | *
338 | * @param der der
339 | * @param name name
340 | * @return String
341 | */
342 | public static String toPem(ByteString der, String name) {
343 | String b64 = Base64.encodeBytes(der.getBytes());
344 | StringBuilder lines = new StringBuilder();
345 | lines.append(String.format("-----BEGIN %s-----\n", name));
346 | for (int start = 0; start < b64.length(); start += 64) {
347 | int end = start + 64 > b64.length() ? b64.length() : start + 64;
348 | lines.append(String.format("%s\n", b64.substring(start, end)));
349 | }
350 | lines.append(String.format("-----END %s-----\n", name));
351 | return lines.toString();
352 | }
353 | }
--------------------------------------------------------------------------------
/src/main/java/com/starkbank/ellipticcurve/utils/File.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve.utils;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.*;
5 |
6 | /*
7 | *This class handles the fileinput types as filename string
8 | *If using bytearray file as "signatureBinary.txt"
9 | *Use the readByte method
10 | **/
11 | public class File {
12 |
13 | /**
14 | *
15 | * @param fileName fileName
16 | * @return String
17 | */
18 | public static String read(String fileName)
19 | {
20 | String content = "";
21 | try
22 | {
23 | content = new String(Files.readAllBytes(Paths.get(fileName)));
24 | }
25 | catch (IOException e)
26 | {
27 | e.printStackTrace();
28 | }
29 | return content;
30 | }
31 |
32 | /**
33 | *
34 | * @param fileName fileName
35 | * @return byte[]
36 | */
37 | public static byte[] readBytes(String fileName)
38 | {
39 | byte[] content = null;
40 | try
41 | {
42 | content = Files.readAllBytes(Paths.get(fileName));
43 | }
44 | catch (IOException e)
45 | {
46 | e.printStackTrace();
47 | }
48 |
49 | return content;
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/src/main/java/com/starkbank/ellipticcurve/utils/RandomInteger.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve.utils;
2 | import java.math.BigInteger;
3 | import java.security.SecureRandom;
4 | import java.util.Random;
5 |
6 |
7 | public class RandomInteger {
8 |
9 | /**
10 | *
11 | * @param start start
12 | * @param end end
13 | * @return BigInteger
14 | */
15 | public static BigInteger between(BigInteger start, BigInteger end) {
16 | Random random = new SecureRandom();
17 | return new BigInteger(end.toByteArray().length * 8 - 1, random).abs().add(start);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/com/starkbank/ellipticcurve/EcdsaTest.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve;
2 | import org.junit.Test;
3 |
4 | import java.math.BigInteger;
5 |
6 | import static org.junit.Assert.assertFalse;
7 | import static org.junit.Assert.assertTrue;
8 |
9 |
10 | public class EcdsaTest {
11 |
12 | @Test
13 | public void testVerifyRightMessage() {
14 | PrivateKey privateKey = new PrivateKey();
15 | PublicKey publicKey = privateKey.publicKey();
16 |
17 | String message = "This is the right message";
18 |
19 | Signature signature = Ecdsa.sign(message, privateKey);
20 | assertTrue(Ecdsa.verify(message, signature, publicKey));
21 | }
22 |
23 | @Test
24 | public void testVerifyWrongMessage() {
25 | PrivateKey privateKey = new PrivateKey();
26 | PublicKey publicKey = privateKey.publicKey();
27 |
28 | String message1 = "This is the right message";
29 | String message2 = "This is the wrong message";
30 |
31 | Signature signature = Ecdsa.sign(message1, privateKey);
32 |
33 | assertFalse(Ecdsa.verify(message2, signature, publicKey));
34 | }
35 |
36 | @Test
37 | public void testZeroSignature() {
38 | PrivateKey privateKey = new PrivateKey();
39 | PublicKey publicKey = privateKey.publicKey();
40 |
41 | String message = "This is the right message";
42 |
43 | assertFalse(Ecdsa.verify(message, new Signature(BigInteger.ZERO, BigInteger.ZERO), publicKey));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/com/starkbank/ellipticcurve/OpenSSLTest.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve;
2 | import com.starkbank.ellipticcurve.utils.ByteString;
3 | import org.junit.Test;
4 | import java.io.IOException;
5 | import java.net.URISyntaxException;
6 | import static org.junit.Assert.assertTrue;
7 |
8 |
9 | public class OpenSSLTest {
10 |
11 | @Test
12 | public void testAssign() throws URISyntaxException, IOException {
13 | // Generated by:openssl ecparam -name secp256k1 - genkey - out privateKey.pem
14 | String privateKeyPem = Utils.readFileAsString("privateKey.pem");
15 |
16 | PrivateKey privateKey = PrivateKey.fromPem(privateKeyPem);
17 |
18 | String message = Utils.readFileAsString("message.txt");
19 |
20 | Signature signature = Ecdsa.sign(message, privateKey);
21 |
22 | PublicKey publicKey = privateKey.publicKey();
23 |
24 | assertTrue(Ecdsa.verify(message, signature, publicKey));
25 | }
26 |
27 | @Test
28 | public void testVerifySignature() throws IOException, URISyntaxException {
29 | // openssl ec -in privateKey.pem - pubout - out publicKey.pem
30 | String publicKeyPem = Utils.readFileAsString("publicKey.pem");
31 | // openssl dgst -sha256 -sign privateKey.pem -out signature.binary message.txt
32 | ByteString signatureBin = new ByteString(Utils.readFileAsBytes("signature.binary"));
33 |
34 | String message = Utils.readFileAsString("message.txt");
35 |
36 | PublicKey publicKey = PublicKey.fromPem(publicKeyPem);
37 |
38 | Signature signature = Signature.fromDer(signatureBin);
39 |
40 | assertTrue(Ecdsa.verify(message, signature, publicKey));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/com/starkbank/ellipticcurve/PrivateKeyTest.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve;
2 | import com.starkbank.ellipticcurve.utils.ByteString;
3 | import org.junit.Test;
4 | import static org.junit.Assert.assertEquals;
5 |
6 |
7 | public class PrivateKeyTest {
8 |
9 | @Test
10 | public void testPemConversion() {
11 | PrivateKey privateKey1 = new PrivateKey();
12 | String pem = privateKey1.toPem();
13 | PrivateKey privateKey2 = PrivateKey.fromPem(pem);
14 | assertEquals(privateKey1.secret, privateKey2.secret);
15 | assertEquals(privateKey1.curve, privateKey2.curve);
16 | }
17 |
18 | @Test
19 | public void testDerConversion() {
20 | PrivateKey privateKey1 = new PrivateKey();
21 | ByteString der = privateKey1.toDer();
22 | PrivateKey privateKey2 = PrivateKey.fromDer(der);
23 | assertEquals(privateKey1.secret, privateKey2.secret);
24 | assertEquals(privateKey1.curve, privateKey2.curve);
25 | }
26 |
27 | @Test
28 | public void testStringConversion() {
29 | PrivateKey privateKey1 = new PrivateKey();
30 | ByteString string = privateKey1.toByteString();
31 | PrivateKey privateKey2 = PrivateKey.fromString(string);
32 | assertEquals(privateKey1.secret, privateKey2.secret);
33 | assertEquals(privateKey1.curve, privateKey2.curve);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/com/starkbank/ellipticcurve/PublicKeyTest.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve;
2 | import com.starkbank.ellipticcurve.utils.ByteString;
3 | import org.junit.Test;
4 | import static org.junit.Assert.assertEquals;
5 |
6 |
7 | public class PublicKeyTest {
8 |
9 | @Test
10 | public void testPemConversion() {
11 | PrivateKey privateKey = new PrivateKey();
12 | PublicKey publicKey1 = privateKey.publicKey();
13 | String pem = publicKey1.toPem();
14 | PublicKey publicKey2 = PublicKey.fromPem(pem);
15 | assertEquals(publicKey1.point.x, publicKey2.point.x);
16 | assertEquals(publicKey1.point.y, publicKey2.point.y);
17 | assertEquals(publicKey1.curve, publicKey2.curve);
18 | }
19 |
20 | @Test
21 | public void testDerConversion() {
22 | PrivateKey privateKey = new PrivateKey();
23 | PublicKey publicKey1 = privateKey.publicKey();
24 | ByteString der = publicKey1.toDer();
25 | PublicKey publicKey2 = PublicKey.fromDer(der);
26 | assertEquals(publicKey1.point.x, publicKey2.point.x);
27 | assertEquals(publicKey1.point.y, publicKey2.point.y);
28 | assertEquals(publicKey1.curve, publicKey2.curve);
29 | }
30 |
31 | @Test
32 | public void testStringConversion() {
33 | PrivateKey privateKey = new PrivateKey();
34 | PublicKey publicKey1 = privateKey.publicKey();
35 | ByteString string = publicKey1.toByteString();
36 | PublicKey publicKey2 = PublicKey.fromString(string);
37 | assertEquals(publicKey1.point.x, publicKey2.point.x);
38 | assertEquals(publicKey1.point.y, publicKey2.point.y);
39 | assertEquals(publicKey1.curve, publicKey2.curve);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/java/com/starkbank/ellipticcurve/SignatureTest.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve;
2 | import com.starkbank.ellipticcurve.utils.ByteString;
3 | import org.junit.Test;
4 | import static org.junit.Assert.assertEquals;
5 |
6 |
7 | public class SignatureTest {
8 |
9 | @Test
10 | public void testDerConversion() {
11 | PrivateKey privateKey = new PrivateKey();
12 | String message = "This is a text message";
13 |
14 | Signature signature1 = Ecdsa.sign(message, privateKey);
15 |
16 | ByteString der = signature1.toDer();
17 |
18 | Signature signature2 = Signature.fromDer(der);
19 |
20 | assertEquals(signature1.r, signature2.r);
21 | assertEquals(signature1.s, signature2.s);
22 | }
23 |
24 | @Test
25 | public void testBase64Conversion() {
26 | PrivateKey privateKey = new PrivateKey();
27 | String message = "This is a text message";
28 |
29 | Signature signature1 = Ecdsa.sign(message, privateKey);
30 |
31 | String base64 = signature1.toBase64();
32 |
33 | Signature signature2 = Signature.fromBase64(new ByteString(base64.getBytes()));
34 |
35 | assertEquals(signature1.r, signature2.r);
36 | assertEquals(signature1.s, signature2.s);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/java/com/starkbank/ellipticcurve/Utils.java:
--------------------------------------------------------------------------------
1 | package com.starkbank.ellipticcurve;
2 | import java.io.IOException;
3 | import java.io.RandomAccessFile;
4 | import java.net.URISyntaxException;
5 |
6 |
7 | class Utils {
8 |
9 | static String readFileAsString(String path) throws URISyntaxException, IOException {
10 | return new String(readFileAsBytes(path), "ASCII");
11 | }
12 |
13 | static byte[] readFileAsBytes(String path) throws URISyntaxException {
14 | return read(ClassLoader.getSystemClassLoader().getResource(path).toURI().getPath());
15 | }
16 |
17 | private static byte[] read(String path) {
18 | try {
19 | RandomAccessFile f = new RandomAccessFile(path, "r");
20 | if (f.length() > Integer.MAX_VALUE)
21 | throw new RuntimeException("File is too large");
22 | byte[] b = new byte[(int) f.length()];
23 | f.readFully(b);
24 | if (f.getFilePointer() != f.length())
25 | throw new RuntimeException("File length changed while reading");
26 | return b;
27 | } catch (IOException e) {
28 | throw new RuntimeException("Could not read file");
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/resources/message.txt:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "amount": 50000,
4 | "receiver": "",
5 | "description": "Sample transfer between workspaces",
6 | "externalId": "123456",
7 | "tags": ["john", "smith"]
8 | }
9 | }
--------------------------------------------------------------------------------
/src/test/resources/privateKey.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN EC PARAMETERS-----
2 | BgUrgQQACg==
3 | -----END EC PARAMETERS-----
4 | -----BEGIN EC PRIVATE KEY-----
5 | MHQCAQEEIODvZuS34wFbt0X53+P5EnSj6tMjfVK01dD1dgDH02RzoAcGBSuBBAAK
6 | oUQDQgAE/nvHu/SQQaos9TUljQsUuKI15Zr5SabPrbwtbfT/408rkVVzq8vAisbB
7 | RmpeRREXj5aog/Mq8RrdYy75W9q/Ig==
8 | -----END EC PRIVATE KEY-----
9 |
--------------------------------------------------------------------------------
/src/test/resources/publicKey.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE/nvHu/SQQaos9TUljQsUuKI15Zr5SabP
3 | rbwtbfT/408rkVVzq8vAisbBRmpeRREXj5aog/Mq8RrdYy75W9q/Ig==
4 | -----END PUBLIC KEY-----
5 |
--------------------------------------------------------------------------------
/src/test/resources/signature.binary:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starkbank/ecdsa-java/1a133705dfa47090914658a8f90b2101c7e62092/src/test/resources/signature.binary
--------------------------------------------------------------------------------
/src/test/resources/signatureBinary.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starkbank/ecdsa-java/1a133705dfa47090914658a8f90b2101c7e62092/src/test/resources/signatureBinary.txt
--------------------------------------------------------------------------------