tag:blogger.com,1999:blog-3830351718376189402024-03-26T09:35:54.524-05:00Michael RemijanMy thoughts on technology
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.comBlogger84125tag:blogger.com,1999:blog-383035171837618940.post-77571896628149051902023-03-11T16:02:00.009-06:002023-03-13T12:56:26.767-05:00Using PrismJS in Blogger for Code Highlighting<h1 id="abstract">Abstract</h1>
<p>For my blog, since I write mostly about technology and specifically software development, I needed a syntax highlighter for styling my source code examples.</p>
<p>I first started using <a href="https://github.com/syntaxhighlighter" title="SyntaxHighlighter">SyntaxHighlighter</a> to style source code. SyntaxHighligher works by add a class to a <pre> tag like this:</p>
<p><pre class=“brush: java”></p>
<p>This worked well until I started writing my blog posts using <a href="https://www.literatureandlatte.com/scrivener/overview" title="Scrivener">Scrivener</a>. With Scrivener, I write in <a href="https://www.markdownguide.org/" title="Markdown">Markdown</a> and Scrivener compiles to HTML for me. The standard HTML to use for source code is a <pre> tag surrounding a <code> tag like this:</p>
<p><pre><code class=“java”></p>
<p>Unfortunately, SyntaxHighligher does not support this HTML so it no longer worked for me.</p>
<p>I then started using <a href="https://highlightjs.org/download/" title="Highlight.js">Highlight.js</a>. This tool has been working well, but its styling is a little too simple. Plus I really wanted to start having line numbers added to my source code examples and Highlight.js does not support this.</p>
<p>It is time for another change. The purpose of this post is demonstrate how to incorporate the <a href="https://prismjs.com/" title="PrismJS">PrismJS</a> syntax highlighting tool into <a href="https://www.blogger.com" title="Blogger">Blogger</a>.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>Blogger</li>
<li>Scrivener 3</li>
<li>PrismJS 1.29</li>
</ul>
<h1 id="downloadprismjs">Download PrismJS</h1>
<p>Visit the PrismJS download page: <a href="https://prismjs.com/download.html">https://prismjs.com/download.html</a>. On this page you are able to select the languages and plugins you want to include in your prism download. It is tempting to select everything, but, the reality is you will never use some of the languages listed. It only takes a few minutes to go through the language list and select the ones you use most often.</p>
<p>For PrismJS plugins, my primary reason for switching to PrismJS is the <strong>Line Numbers</strong> plugin. This is an important feature for me. I want to have line numbers added to my source code examples.</p>
<p>Another important plugin is the <strong>Autoloader</strong> plugin. If you try to style a language you have not previously included in your download, <strong>Autoloader</strong> will automatically get the styling for that language for you. This is good for occasional use. I would not rely on it all the time. If you start blogging about a new language regularly, re-download PrismJS with that language selected.</p>
<p>What is also nice about the PrismJS download page is that while you are selecting languages and plugins, the URL in your browser is automatically updated to reflect your selections. This means, once you are done selecting all the options you want, save the URL in your your favorite note-taking software (OneNote). Then all you need to do is click on the URL and you don’t have to go through selecting all your languages and plugins again. Very nice!</p>
<p>These are my selections: <a href="https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+http+java+javadoc+javadoclike+json+json5+plsql+python+regex+sql+typescript+yaml&plugins=line-numbers+autoloader">https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+http+java+javadoc+javadoclike+json+json5+plsql+python+regex+sql+typescript+yaml&plugins=line-numbers+autoloader</a></p>
<h1 id="hostyourprismjsdownloadononedrive">Host Your PrismJS Download on OneDrive</h1>
<p>After you have downloaded the Prism JS and CSS files, the next thing you need to figure out is what to do with them. There are a couple options:</p>
<ol>
<li>Cut and paste the contents inside your Blogger theme.</li>
<li>Host the JS and CSS files somewhere and update your Blogger template to use them.</li>
</ol>
<p>Although option #2 is a bit more complicated, it is the better long-term option in my opinion. Blogger does not allow file uploads, so the files have to be hosted somewhere else. There are a number of different options where to host the files, but I chose to use my Microsoft OneDrive account to do this. I chose OneDrive because I already have an account, I use it all the time, and it is easy to use. Most online file upload system (Google Drive, etc.) allow you to get a read-only, permanent link to a file. That is exactly what we are going to to do.</p>
<p>I’m assuming you already know how to save a file on OneDrive, so I will start from there. Login to OneDrive with a web browser and navigate to the folder where you have the Prism JS and CSS files. As seen in Figure 1, you will need to click three times to generate the embedded code.</p>
<ol>
<li>Select one of the files.</li>
<li>Click the “Embed” option.</li>
<li>Click the “Generate” button.</li>
</ol>
<h1 id="figure1-threeclickstogenerateembeddedcode">Figure 1 - Three Clicks to Generate Embedded Code</h1>
<figure>
<img src="https://bnz06pap001files.storage.live.com/y4mbA2WM5zosc4tlm4ISfhPHFkdfyIHRnO8GGne80_aVfWaDj4ATW91ePQbbyV8KnvRqSHOuZP3d6_LTjAeGNMSvz_4CpeGzX35MkNoY4_IkJha6RD2rAJgictegmIycvz4e2NsexuZjI97Wozkl7Zc909z-XCcAllfZlQp4eyB9Qgv10cf_7nlnBewC6Aqaxdi?width=1396&height=638&cropmode=none" alt="Three Clicks to Generate Embedded Code" title="Three Clicks to Generate Embedded Code" />
<figcaption>Three Clicks to Generate Embedded Code</figcaption>
</figure>
<p>Figure 2 shows an embedded code example. You will notice it is an <iframe> tag with a <strong>src</strong> attribute (and a few others). We will be concentrating on the <strong>src</strong> attribute.</p>
<h1 id="figure2-theiframeembeddedcode">Figure 2 - The iframe Embedded Code</h1>
<figure>
<img src="https://bnz06pap001files.storage.live.com/y4mXazI67bHLRfYyCR7nNEbo1Md28Sj5D8K5cDcl-4349NOX3PGbEl1Zn0yNu9Uhl1JupG-RA1_ZeXUdh_4fbPYvuLBX_0iuisQWLvvfvrZIn4thPzTMoOG61NJok2-SGZ8KEJp7YNKpRcew5ZssVM9bVwuZgpGi4D2R2WSofFWfDEUFnSfusPxo5CbVE6OSiyE?width=323&height=161&cropmode=none" alt="The iframe Embedded Code" title="The iframe Embedded Code" />
<figcaption>The iframe Embedded Code</figcaption>
</figure>
<p>Let us take a look at the <iframe> tag a little more closely:</p>
<p><iframe src=“<strong>https://onedrive.live.com/embed?cid=0C5144D8101C068D&resid=C5144D8101C068D%2127125&authkey=ADxgzT72UZ6zQQM</strong>” width=“98” height=“120” frameborder=“0” scrolling=“no”></iframe></p>
<p>Extract the <strong>bolded URL</strong> like this:</p>
<p>https://onedrive.live.com/embed?cid=0C5144D8101C068D&resid=C5144D8101C068D%2127125&authkey=ADxgzT72UZ6zQQM</p>
<p>Then changed <strong>embed</strong> to <strong>download</strong> like this:</p>
<p>https://onedrive.live.com/<strong>download</strong>?cid=0C5144D8101C068D&resid=C5144D8101C068D%2127125&authkey=ADxgzT72UZ6zQQM</p>
<p>You will want to do this for both the JS file and the CSS file. When you are done you will have two URL values that look like this.</p>
<p><strong>JS</strong>. https://onedrive.live.com/download?cid=0C5144D8101C068D&resid=C5144D8101C068D%2127124&authkey=AIhs3YZuWx_nl8k</p>
<p><strong>CSS</strong>. https://onedrive.live.com/download?cid=0C5144D8101C068D&resid=C5144D8101C068D%2127125&authkey=ADxgzT72UZ6zQQM</p>
<p>These URL values are the direct links to your JS file and the CSS file. Test them by pasting the URL values into a browser. The browser should download the file without redirecting to OneDrive. If it does not download directly, something is not right and you should try again.</p>
<p>Now that we have the direct links to the hosted JS and CSS files, let us look at how we update the Blogger theme.</p>
<h1 id="bloggerthemeupdates">Blogger Theme Updates</h1>
<p>Now that the Prism JS and CSS files are hosted on OneDrive and I have permanent URL values to retrieve them, I now need to update my Blogger theme to use these files. I will need to make two updates to the Blogger theme:</p>
<ol>
<li>Include both the JS and CSS files.</li>
<li>Add the ‘line-numbers’ class to the <body> tag.</li>
</ol>
<p>Let us take a look at how to do both.</p>
<h2 id="includeboththejsandcssfiles">Include both the JS and CSS files</h2>
<p>The JS file gets included with a <strong><script></strong> tag and the CS file gets included with a <strong><link></strong> tag. Start by creating both of these tags and drop in the permanent URL values like this:</p>
<p><strong>JS</strong>. <script src=‘https://onedrive.live.com/download?cid=0C5144D8101C068D&resid=C5144D8101C068D%2127124&authkey=AIhs3YZuWx_nl8k’ type=‘text/javascript’/></p>
<p><strong>CSS</strong>. <link href=‘https://onedrive.live.com/download?cid=0C5144D8101C068D&resid=C5144D8101C068D%2127125&authkey=ADxgzT72UZ6zQQM’ rel=‘stylesheet’/></p>
<p>However, your not done yet! Normally this is all you need to do, but Blogger themes have a bit of a quirk. They seem to be saved as XML so the <strong>&</strong> characters in the URL values are a problem. To successfully save these tags to the Blogger theme, you need to escape the <strong>&</strong> characters with <strong>&amp;</strong> like this:</p>
<p><strong>JS</strong>. <script src=‘https://onedrive.live.com/download?cid=0C5144D8101C068D<strong>&amp;</strong>resid=C5144D8101C068D%2127124<strong>&amp;</strong>authkey=AIhs3YZuWx_nl8k’ type=‘text/javascript’/></p>
<p><strong>CSS</strong>. <link href=‘https://onedrive.live.com/download?cid=0C5144D8101C068D<strong>&amp;</strong>resid=C5144D8101C068D%2127125<strong>&amp;</strong>authkey=ADxgzT72UZ6zQQM’ rel=‘stylesheet’/></p>
<p>These tags are now ready to be used in the Blogger theme.</p>
<h2 id="addtheline-numbersclasstotheltbodygttag">Add the ‘line-numbers’ class to the <body> tag</h2>
<p>Recall that one of the reasons I am switching to Prism is because it was important to me to have line numbers added to my source code examples. This is done by the Prism <strong>Line Numbers</strong> plugin. To use this plugin, you need to do is add the ‘line-numbers’ class to the <strong><body></strong> tag within the Blogger theme. It looks like this:</p>
<p><body expr:class=‘&quot;loading <strong>line-numbers</strong>&quot; + data:blog.mobileClass’></p>
<p>Now let us get these updates into the Blogger theme.</p>
<h2 id="updatingthebloggertheme">Updating the Blogger Theme</h2>
<p>After you log into your blog, perform the following steps as shown in Figures 3 and 4:</p>
<ol>
<li>Click “Theme” on the left</li>
<li>Click the down-pointing arrow</li>
<li>Select “Edit HTML”</li>
</ol>
<h1 id="figure3-bloggerthemecustomization">Figure 3 - Blogger Theme Customization</h1>
<figure>
<img src="https://bnz06pap001files.storage.live.com/y4mWQmAmnIkCHTfGbGGsyQaPFelFHKfXoJe-jqWbNthx3YOCJWW9kS4B9A2zfDG2MFfwAMz9omo1gkIMLWhgo6GL2a3Z3IHdKxwVK8njMTXgS2PsxrQ09phVfxpCFmm9ybry7vUbHjBbUYstx3q7nBaOq-hfUHjv1LGecJ1uUcz10tq4_j9dM5CLg1TEY-zfCb9?width=1004&height=708&cropmode=none" alt="Blogger Theme Customization" title="Blogger Theme Customization" />
<figcaption>Blogger Theme Customization</figcaption>
</figure>
<h1 id="figure4-bloggeredithtml">Figure 4 - Blogger Edit HTML</h1>
<figure>
<img src="https://bnz06pap001files.storage.live.com/y4maGJVLzu0-AnJ46-kGIOcfpxxp7c-q_-m69ahhh8X3MSlCygGdZI1oku7V8--9XL97o-iM-KFTKLjmsC8u3lE7o4c3o0V6s16bhzoHO3iKyP-B8mFNHYxYFYct3oh3IBsct3rl0I5uDH9ToOuNHHWfZjYJK1kLcl-kPUW03M92XTw66vP3NKWCgQ-m7RyAH6U?width=469&height=389&cropmode=none" alt="Blogger Edit HTML" title="Blogger Edit HTML" />
<figcaption>Blogger Edit HTML</figcaption>
</figure>
<p>Your are now looking at the HTML template of your blog’s theme. The Prism <strong><script></strong> and <strong><link></strong> tags you created above need to go somewhere within the opening and closing <strong><head></head></strong> tags so it looks like this:</p>
<p><head></p>
<p><script src=‘https://onedrive.live.com/download?cid=0C5144D8101C068D<strong>&amp;</strong>resid=C5144D8101C068D%2127124<strong>&amp;</strong>authkey=AIhs3YZuWx_nl8k’ type=‘text/javascript’/>
<link href=‘https://onedrive.live.com/download?cid=0C5144D8101C068D<strong>&amp;</strong>resid=C5144D8101C068D%2127125<strong>&amp;</strong>authkey=ADxgzT72UZ6zQQM’ rel=‘stylesheet’/></p>
<p></head></p>
<p>The update to <strong><body></strong> is even easier. Just search the template for “<body” and update it to include the “line-numbers” class.</p>
<blockquote>
<p><strong>NOTE</strong>
An HTML element can have multiple class values. The values are separated by a blank space. So note in the example below the blank space between <em>loading</em> and <em>line-numbers</em>.</p>
</blockquote>
<p><body expr:class=‘&quot;loading <strong>line-numbers</strong>&quot; + data:blog.mobileClass’></p>
<p>That’s it! Save the file and you are good to go!</p>
<h1 id="examples">Examples</h1>
<p>Let us take a look at a few source code syntax highlighting examples to make sure everything is working OK.</p>
<h1 id="listing1-java">Listing 1 - Java</h1>
<pre><code class="language-java">package org.prism.example;
public static final void main(String [] args) {
System.out.println("Hello world!");
}
</code></pre>
<h1 id="listing2-javascript">Listing 2 - JavaScript</h1>
<pre><code class="language-javascript">const baseValue = prompt('Enter the base of a triangle: ');
const heightValue = prompt('Enter the height of a triangle: ');
// calculate the area
const areaValue = (baseValue * heightValue) / 2;
console.log(
`The area of the triangle is ${areaValue}`
);
</code></pre>
<h1 id="listing3-typescript">Listing 3 - TypeScript</h1>
<pre><code class="language-typescript">class Employee {
id: number;
firstName: string;
lastName: string;
constructor(id: number, firstName: string, lastName: string)
{
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return this.firstName + ' ' + this.lastName;
}
}
// create Employee class object
let employee = new Employee(100, 'Rita', 'Red');
console.log(employee);
console.log(employee.getFullName());
</code></pre>
<h1 id="listing4-plsql">Listing 4 - PL/SQL</h1>
<pre><code class="language-sql">DECLARE
name VARCHAR2(50);
BEGIN
name := 'Rita';
DBMS_OUTPUT.PUT_LINE('Hello, ' || name);
END;
FOR i IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('i = ' || i);
END LOOP;
</code></pre>
<h1 id="listing5-xml">Listing 5 - XML</h1>
<pre><code class="language-xml"><Catalog>
<CD>
<Title>Empire Burlesque</Title>
<Artist>Bob Dylan</Artist>
<Country>USA</Country>
<Company>Columbia</Company>
<Price>10.90</Price>
<Year>1985</Year>
</CD>
<CD>
<Title>Greatest Hits</Title>
<Artist>Dolly Parton</Artist>
<Country>USA</Country>
<Company>RCA</Company>
<Price>9.90</Price>
<Year>1982</Year>
</CD>
</Catalog>
</code></pre>
<h1 id="listing6-html">Listing 6 - HTML</h1>
<pre><code class="language-html"><!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>
</code></pre>
<h1 id="listing7-json">Listing 7 - JSON</h1>
<pre><code class="language-json">{
"colors": [
{
"color": "red",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,0,0,1],
"hex": "#FF0"
}
},
{
"color": "blue",
"category": "hue",
"type": "primary",
"code": {
"rgba": [0,0,255,1],
"hex": "#00F"
}
},
{
"color": "yellow",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,255,0,1],
"hex": "#FF0"
}
},
{
"color": "green",
"category": "hue",
"type": "secondary",
"code": {
"rgba": [0,255,0,1],
"hex": "#0F0"
}
}
]
}
</code></pre>
<h1 id="summary">Summary</h1>
<p>PrismJS is a nice syntax highlighter for source code examples. Using it with Blogger is a little work, but not too complicated.</p>
<p>Visit the PrismJS download page: <a href="https://prismjs.com/download.html">https://prismjs.com/download.html</a> and download what you will use most often.</p>
<p>Host the downloaded JS and CSS files on the technology of your choice. My example used OneDrive. You can also use Google Drive, GitLab, AWS, Azure, and I’m sure there are others.</p>
<p>Include the JS and CSS files in the Blogger theme by updating the template:</p>
<p><head></p>
<p><script src=‘https://onedrive.live.com/download?cid=0C5144D8101C068D<strong>&amp;</strong>resid=C5144D8101C068D%2127124<strong>&amp;</strong>authkey=AIhs3YZuWx_nl8k’ type=‘text/javascript’/>
<link href=‘https://onedrive.live.com/download?cid=0C5144D8101C068D<strong>&amp;</strong>resid=C5144D8101C068D%2127125<strong>&amp;</strong>authkey=ADxgzT72UZ6zQQM’ rel=‘stylesheet’/></p>
<p></head></p>
<p>Add the “line-numbers” class to the <strong><body></strong> tag to support the <strong>Line Numbers</strong> plugin.</p>
<p><body expr:class=‘&quot;loading <strong>line-numbers</strong>&quot; + data:blog.mobileClass’></p>
<p>Save the template change, create a blog with source code examples, and that’s it!</p>
<p>Enjoy!</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-48463790174206445352023-02-02T10:51:00.004-06:002023-08-30T12:12:42.174-05:00Creating, Signing, and Verifying JWT in Java
<h1 id="abstract">Abstract</h1>
<p>You use JWTs don’t you? Everyone does, right? But do you know how to generate, sign, and verify them? The purpose of this post is to demonstrate how to code all these operations.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>Java 11</li>
<li>Maven 3.8.6 (Bundled with NetBeans)</li>
</ul>
<h1 id="download">Download</h1>
<p>Visit my GitHub page <a href="https://github.com/mjremijan">https://github.com/mjremijan</a> to see all of my open source projects. The code for this post is located at: <a href="https://github.com/mjremijan/thoth-jwt" title="thoth-jwt">https://github.com/mjremijan/thoth-jwt</a></p>
<h1 id="introduction">Introduction</h1>
<p>A JWT is a simple three-part string of encoded characters - header, payload, signature - separated by 2 “.” characters.</p>
<pre><code class="text">xxxxx.yyyyy.zzzzz
</code></pre>
<p>JWT technology has been around for years. Read all about the JWT specification on the <em>Introduction to JSON Web Tokens</em> at <a href="https://jwt.io/introduction">https://jwt.io/introduction</a>. This blog focuses on the Java code to create and verify JWT values. There are 2 examples:</p>
<ol>
<li>JWT with Symmetric HMAC SHA256 Signature</li>
<li>JWT with Asymmetric RSA SHA256 Signature</li>
</ol>
<p>Let’s take a look at them.</p>
<h1 id="jwtwithsymmetrichmacsha256signature">JWT with Symmetric HMAC SHA256 Signature</h1>
<p>Listing 1 shows the code and Listing 2 shows example output.</p>
<h1 id="listing1-jwtwithsymmetrichmacsha256signature">Listing 1 - JWT with Symmetric HMAC SHA256 Signature</h1>
<pre><code class="java">1. package org.thoth.jwt.main;
2.
3. import java.util.Base64;
4. import javax.crypto.Mac;
5. import javax.crypto.spec.SecretKeySpec;
6.
7. /**
8. *
9. * @author Michael Remijan mjremijan@yahoo.com @mjremijan
10. */
11. public class SignatureWithSymmetricalHmacSha256Main
12. {
13. public static void main(String[] args) throws Exception
14. {
15. // JWT HEADER
16. //
17. // This is the xxxxx of a JWT xxxxx.yyyyy.zzzzz
18. //
19. // Given the following JSON document, encode it
20. // using Java as defined in the JWT specifications
21. String header = "{\"alg\":\"HS256\",\"typ\": \"JWT\"}";
22. String headerEncoded
23. = Base64.getUrlEncoder()
24. .withoutPadding()
25. .encodeToString(
26. header.getBytes()
27. );
28. String headerDecoded
29. = new String(
30. Base64.getUrlDecoder().decode(headerEncoded)
31. );
32.
33. System.out.printf("Header Plain : %s%n", header);
34. System.out.printf("Header Encoded : %s%n", headerEncoded);
35. System.out.printf("Header Decoded : %s%n", headerDecoded);
36.
37.
38. // JWT PAYLOAD
39. //
40. // This is the yyyyy of a JWT xxxxx.yyyyy.zzzzz
41. //
42. // Given the following JSON document, encode it
43. // using Java as defined in the JWT specifications
44. String payload = "{\"sub\":\"TMJR00001\",\"name\":\"Michael J. Remijan\",\"exp\":61475608800,\"iss\":\"info@wstutorial.com\",\"groups\":[\"user\",\"admin\"]}";
45. String payloadEncoded
46. = Base64.getUrlEncoder()
47. .withoutPadding()
48. .encodeToString(
49. payload.getBytes()
50. );
51.
52. String payloadDecoded
53. = new String(
54. Base64.getUrlDecoder().decode(payloadEncoded)
55. );
56.
57. System.out.printf("%n");
58. System.out.printf("Payload Plain : %s%n", payload);
59. System.out.printf("Payload Encoded : %s%n", payloadEncoded);
60. System.out.printf("Payload Decoded : %s%n", payloadDecoded);
61.
62.
63. // SIGNATURE / VERIFY
64. // This is the zzzzz of a JWT xxxxx.yyyyy.zzzzz
65. //
66. // Hash-based message authentication code(HMAC)
67. // is a specific type of message authentication code
68. // (MAC) involving a cryptographic hash function and
69. // a secret cryptographic key. As with any MAC, it
70. // may be used to simultaneously verify both the data
71. // integrity and authenticity of a message.
72. //
73. // A cryptographic hash function (CHF) is any function
74. // that can be used to map data of arbitrary size to
75. // a fixed-size number of n bits that has special
76. // properties desirable for a cryptographic application.
77. //
78. // For this example, the process will use the SHA256
79. // cryptographic hash function and a secret key
80. // to generate a signatureCreatedFromThisData (hash) of the JWT data.
81. // This signatureCreatedFromThisData can then be used to verify the
82. // JWT data has not been tampered.
83. //
84. // Typically the secret key is only available on the
85. // Authentication Server. The key is used to create the
86. // signatureCreatedFromThisData for the JWT. Clients will typically make
87. // an authentication request (HTTPS) to the Authentication
88. // server to verify a JWT. Clients cannot verify a JWT
89. // themselves because they do not have access to the
90. // secret key. However, if a Client is 100% trusted,
91. // The secret key can be shared with the Client so
92. // that the Client can do its own verification.
93. // WARNING: This means the Client will also be able
94. // to make new JWTs, which can be dangerous.
95. String algorithm = "HmacSHA256";
96. String secret = "thisismysupersecretkeywhichshouldonlybeontheauthenticationserver";
97. SecretKeySpec key = new SecretKeySpec(secret.getBytes(), algorithm);
98. Mac mac = Mac.getInstance(algorithm);
99. mac.init(key);
100. String signatureCreatedFromThisData
101. = headerEncoded + "." + payloadEncoded;
102. String signatureEncoded
103. = Base64.getUrlEncoder()
104. .withoutPadding()
105. .encodeToString(mac.doFinal(
106. signatureCreatedFromThisData.getBytes()
107. )
108. );
109.
110. System.out.printf("%n");
111. System.out.printf("Signature Algorithm : %s%n", algorithm);
112. System.out.printf("Signature Secret : %s%n", secret);
113. System.out.printf("Signaure Encoded :%s%n", signatureEncoded);
114. }
115. }
116.
</code></pre>
<h1 id="listing2-hmacexampleoutput">Listing 2 - HMAC Example Output</h1>
<pre><code class="java">Header Plain : {"alg":"HS256","typ": "JWT"}
Header Encoded : eyJhbGciOiJIUzI1NiIsInR5cCI6ICJKV1QifQ
Header Decoded : {"alg":"HS256","typ": "JWT"}
Payload Plain : {"sub":"TMJR00001","name":"Michael J. Remijan","exp":61475608800,"iss":"info@wstutorial.com","groups":["user","admin"]}
Payload Encoded : eyJzdWIiOiJUTUpSMDAwMDEiLCJuYW1lIjoiTWljaGFlbCBKLiBSZW1pamFuIiwiZXhwIjo2MTQ3NTYwODgwMCwiaXNzIjoiaW5mb0B3c3R1dG9yaWFsLmNvbSIsImdyb3VwcyI6WyJ1c2VyIiwiYWRtaW4iXX0
Payload Decoded : {"sub":"TMJR00001","name":"Michael J. Remijan","exp":61475608800,"iss":"info@wstutorial.com","groups":["user","admin"]}
Signature Algorithm : HmacSHA256
Signature Secret : thisismysupersecretkeywhichshouldonlybeontheauthenticationserver
Signaure Encoded :Xi6kVafrGX18FQIkNZuVJVBbmGbmEzI8cM-5G02S32A
</code></pre>
<p>Line #21 of Listing 1 starts the creation of the JWT header. This is the <code>xxxxx</code> part of a <code>xxxxx.yyyyy.zzzzz</code> JWT. As you can see, the code is simple. Use <code>Base64.getUrlEncoder().withoutPadding()</code> for encoding and <code>Base64.getUrlDecoder()</code> for decoding.</p>
<blockquote>
<p><strong>NOTE</strong>
Make sure to use the <code>**.withoutPadding()**</code> encoder. If not, trailing “=” characters will be added by the encoder to make the encoded string the necessary length. These trailing “=” are not allowed by the JWT specification so if you have them, other JWT decoders won’t be able to decode your JWT properly.</p>
</blockquote>
<p>Line #44 of Listing 1 starts the creation of the JWT payload, typically user information, but in theory can be anything. This is the <code>yyyyy</code> part of a <code>xxxxx.yyyyy.zzzzz</code> JWT. As you can see, the code is simple. Use <code>Base64.getUrlEncoder().withoutPadding()</code> for encoding and <code>Base64.getUrlDecoder()</code> for decoding. See <strong>NOTE</strong> above about using the <code>.withoutPadding()</code> encoder.</p>
<p>Line #95 of Listing 1 starts the creation of the JWT signature. This is the <code>zzzzz</code> part of a <code>xxxxx.yyyyy.zzzzz</code> JWT. Listing 1 is an example of using the <code>"alg":"HS256"</code> aka <code>HmacSHA256</code> algorithm. This is a single-key, symmetric algorithm which relies on a user-generated <code>secret</code> value as seen on line #96. This <code>secret</code> typically is stored outside the application in some kind of configuration system (file, git, database, etc.). Staring with line #102, you see how the <code>MAC</code> is used to finish the hash and the <code>Base64.getUrlEncoder().withoutPadding()</code> is used to encode the hash.</p>
<p>You’ll notice that after signing, there is no more code in Listing 1. Where’s the code showing how to verify a JWT? Well with a single-key, symmetric algorithm like <code>HmacSHA256</code>, the signing and verifying steps are exactly the same. To verify, the signature needs to be generated again and compared with the <code>zzzzz</code> part of a <code>xxxxx.yyyyy.zzzzz</code> JWT.</p>
<p>That’s it for JWT with Symmetric HMAC SHA256 Signature.</p>
<h1 id="jwtwithasymmetricrsasha256signature">JWT with Asymmetric RSA SHA256 Signature</h1>
<p>Listing 3 shows the code and Listing 4 shows example output.</p>
<h1 id="listing3-jwtwithasymmetricrsasha256signature">Listing 3 - JWT with Asymmetric RSA SHA256 Signature</h1>
<pre><code class="java">1. package org.thoth.jwt.main;
2.
3. import java.security.KeyPair;
4. import java.security.KeyPairGenerator;
5. import java.security.PrivateKey;
6. import java.security.PublicKey;
7. import java.security.Signature;
8. import java.util.Base64;
9.
10. /**
11. *
12. * @author Michael Remijan mjremijan@yahoo.com @mjremijan
13. */
14. public class SignatureWithAsymmetricalRsaSha256Main
15. {
16. public static void main(String[] args) throws Exception
17. {
18. // JWT HEADER
19. //
20. // This is the xxxxx of a JWT xxxxx.yyyyy.zzzzz
21. //
22. // Given the following JSON document, encode it
23. // using Java as defined in the JWT specifications
24. String header = "{\"alg\":\"RS256\",\"typ\": \"JWT\"}";
25. String headerEncoded
26. = Base64.getUrlEncoder()
27. .withoutPadding()
28. .encodeToString(
29. header.getBytes()
30. );
31. String headerDecoded
32. = new String(
33. Base64.getUrlDecoder().decode(headerEncoded)
34. );
35.
36. System.out.printf("Header Plain : %s%n", header);
37. System.out.printf("Header Encoded : %s%n", headerEncoded);
38. System.out.printf("Header Decoded : %s%n", headerDecoded);
39.
40.
41. // JWT PAYLOAD
42. //
43. // This is the yyyyy of a JWT xxxxx.yyyyy.zzzzz
44. //
45. // Given the following JSON document, encode it
46. // using Java as defined in the JWT specifications
47. String payload = "{\"sub\":\"TMJR00001\",\"name\":\"Michael J. Remijan\",\"exp\":61475608800,\"iss\":\"info@wstutorial.com\",\"groups\":[\"user\",\"admin\"]}";
48. String payloadEncoded
49. = Base64.getUrlEncoder()
50. .withoutPadding()
51. .encodeToString(
52. payload.getBytes()
53. );
54.
55. String payloadDecoded
56. = new String(
57. Base64.getUrlDecoder().decode(payloadEncoded)
58. );
59.
60. System.out.printf("%n");
61. System.out.printf("Payload Plain : %s%n", payload);
62. System.out.printf("Payload Encoded : %s%n", payloadEncoded);
63. System.out.printf("Payload Decoded : %s%n", payloadDecoded);
64.
65.
66. // SIGNATURE
67. //
68. // This is the zzzzz of a JWT xxxxx.yyyyy.zzzzz
69. //
70. // RSA (Rivest--Shamir--Adleman) is a public-key cryptosystem
71. // that is widely used for secure data transmission.
72. // In a public-key cryptosystem, the public key is used for
73. // encryption and the private key is used for decryption. The
74. // private key is also used for creating digital signatures
75. // of data and the public key is used for verifying the
76. // digital signature.
77. //
78. // A cryptographic hash function (CHF) is any function
79. // that can be used to map data of arbitrary size to
80. // a fixed-size number of n bits that has special
81. // properties desirable for a cryptographic application.
82. //
83. // For this example, the process will use the SHA256
84. // cryptographic hash function along with a public/private
85. // keypair and the RSA encryption algorithm to generate
86. // a signature for the JWT.
87. //
88. // The private key is used for creating the signature.
89. //
90. KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
91. keyGenerator.initialize(1024);
92. KeyPair kp = keyGenerator.genKeyPair();
93. PublicKey publicKey = (PublicKey) kp.getPublic();
94. PrivateKey privateKey = (PrivateKey) kp.getPrivate();
95. String algorithm = "SHA256withRSA";
96. String signatureCreatedFromThisData
97. = headerEncoded + "." + payloadEncoded;
98.
99. Signature privateSignature
100. = Signature.getInstance(algorithm);
101. privateSignature.initSign(privateKey);
102.
103. System.out.printf("%n");
104. System.out.printf("Algorithm : %s%n", algorithm);
105. System.out.printf("Public Key : %s%n", Base64.getEncoder().encodeToString(publicKey.getEncoded()));
106. System.out.printf("Private Key : %s%n", Base64.getEncoder().encodeToString(privateKey.getEncoded()));
107.
108. privateSignature.update(signatureCreatedFromThisData.getBytes());
109. String signatureEncoded
110. = Base64.getUrlEncoder()
111. .withoutPadding()
112. .encodeToString(
113. privateSignature.sign()
114. );
115. System.out.printf("%n");
116. System.out.printf("Signaure Encoded : %s%n", signatureEncoded);
117.
118. // VERIFY
119. // This is the zzzzz of a JWT xxxxx.yyyyy.zzzzz
120. //
121. // The public key is used for verifying the signature.
122. //
123. // Becuase the public key is used for creating a signature,
124. // it safe to distribute the public key to Clients so
125. // that Clients can verify the JWT signature without
126. // having to ask the Authentication Server for verification
127. //
128.
129. Signature publicSignature = Signature.getInstance(algorithm);
130. publicSignature.initVerify(publicKey);
131. publicSignature.update(signatureCreatedFromThisData.getBytes());
132. boolean verified = publicSignature.verify(
133. Base64.getUrlDecoder().decode(signatureEncoded)
134. );
135. System.out.printf("Signature Verified (t/f) : %b%n", verified);
136. }
137. }
138.
</code></pre>
<h1 id="listing4-rsaexampleoutput">Listing 4 - RSA Example Output</h1>
<pre><code class="java">Header Plain : {"alg":"RS256","typ": "JWT"}
Header Encoded : eyJhbGciOiJSUzI1NiIsInR5cCI6ICJKV1QifQ
Header Decoded : {"alg":"RS256","typ": "JWT"}
Payload Plain : {"sub":"TMJR00001","name":"Michael J. Remijan","exp":61475608800,"iss":"info@wstutorial.com","groups":["user","admin"]}
Payload Encoded : eyJzdWIiOiJUTUpSMDAwMDEiLCJuYW1lIjoiTWljaGFlbCBKLiBSZW1pamFuIiwiZXhwIjo2MTQ3NTYwODgwMCwiaXNzIjoiaW5mb0B3c3R1dG9yaWFsLmNvbSIsImdyb3VwcyI6WyJ1c2VyIiwiYWRtaW4iXX0
Payload Decoded : {"sub":"TMJR00001","name":"Michael J. Remijan","exp":61475608800,"iss":"info@wstutorial.com","groups":["user","admin"]}
Algorithm : SHA256withRSA
Public Key : MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRxw6Ncvsx0/kDYKwA6pLwn3hSbRdYFBOv1aiBomF7lPfOPfqaTgN2yPN6hErlLAP2d+94ig4uXv7MROXlsn8n7jdr2g5yo/kC92RJwALpffzBlWh29hEadiznWp2u0b0h++Cn4HJejfJpZOek6wurBL/7K2Y2TELOg8eg1uipEwIDAQAB
Private Key : MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANHHDo1y+zHT+QNgrADqkvCfeFJtF1gUE6/VqIGiYXuU9849+ppOA3bI83qESuUsA/Z373iKDi5e/sxE5eWyfyfuN2vaDnKj+QL3ZEnAAul9/MGVaHb2ERp2LOdana7RvSH74Kfgcl6N8mlk56TrC6sEv/srZjZMQs6Dx6DW6KkTAgMBAAECgYBqVyPzZGQeADxtD+ZhmIfgXpaaAh8hURwhuIdxH6WXBg8Qh66v5fgvkPKMGt/0iHmByY6lZiaGLzWuywZXiEKYSl6tpK8WtiY+gyYxOVgFckAKzjBJ4GYb6YvPI5p5/qDFqN/9Ca4vDn9URFEIRSBIc1it8TWzze8x2Ljd4vu54QJBAOmlSJ/m4dHJMzLnyM6Y1x/e2fqm48DbfV3m+jDjyR7YrTwcVoZSC17B1z4J7W+/Ea7N61UWRRvelvC4c8OKkEkCQQDl2StC7vbKCsnDAFyDjUADq7p2aE+vVH7v7ZUjHhsTXMF8TFMgfkxl5cH58nDNq1Yo82SKGvMeRnmBYHeHlqZ7AkBP1Ur4YBJ+9QmKdkpV1UGEQUgn7ghaKGUwxbBtLhfVc2HV7TTfVn9OFFuwdgHsMdQf73peq2pXuHnIrK3ZfaoJAkEAh0nXg/NCAdRtw8C/s5L9feujujRVyt6SRMj0ApKi3ze2j2Ihf7u3XjbpgSRprzVNZpc0s3F/bm+O708HrCBJZwJBANPeVhBizgqPZOiQxLRxpNN2+EvEfs8js7YFRwB45orM/+9yVelNojEKxcHT7zS6j59dTlwvbGp6LVrKCrwtwLw=
Signaure Encoded : HO4FLrLDt4ObECVWRiUGIGUimU1M70Y9aILT5op0UkV-kbEx8AqjCsLTh-Y1zOAisvFmuH5LYRw1wQyncQ5uEUWJYcoeldr-1_uFlpD2LqUy-QZfng8e6pxXOopL8Of_OcNEOqRijmI_dob8Gf0UnT7GQWpGTl32cIuuIFDeRHo
Signature Verified (t/f) : true
</code></pre>
<p>Line #24 of Listing 3 starts the creation of the JWT header. This is the <code>xxxxx</code> part of a <code>xxxxx.yyyyy.zzzzz</code> JWT. As you can see, the code is simple. Use <code>Base64.getUrlEncoder().withoutPadding()</code> for encoding and <code>Base64.getUrlDecoder()</code> for decoding.</p>
<blockquote>
<p><strong>NOTE</strong>
Make sure to use the <code>**.withoutPadding()**</code> encoder. If not, trailing “=” characters will be added by the encoder to make the encoded string the necessary length. These trailing “=” are not allowed by the JWT specification so if you have them, other JWT decoders won’t be able to decode your JWT properly.</p>
</blockquote>
<p>Line #47 of Listing 3 starts the creation of the JWT payload, typically user information, but in theory can be anything. This is the <code>yyyyy</code> part of a <code>xxxxx.yyyyy.zzzzz</code> JWT. As you can see, the code is simple. Use <code>Base64.getUrlEncoder().withoutPadding()</code> for encoding and <code>Base64.getUrlDecoder()</code> for decoding. See <strong>NOTE</strong> above about using the <code>.withoutPadding()</code> encoder.</p>
<p>Line #90 of Listing 3 starts the creation of the JWT signature. This is the <code>zzzzz</code> part of a <code>xxxxx.yyyyy.zzzzz</code> JWT. Listing 3 is an example of using the <code>"alg":"RS256"</code> aka <code>SHA256withRSA</code> algorithm. This is a two-key, asymmetric algorithm which relies on a public/private keypair created on Line #92. A <code>Signature</code> object is created on line #99 and it is initialized with the <strong>private key</strong>. Staring with line #108, see how the <code>Signature</code> is used to create a signature and the <code>Base64.getUrlEncoder().withoutPadding()</code> is used to encode the signature.</p>
<blockquote>
<p><strong>NOTE</strong>
The public/private keypair will need to be generated outside the application and kept in some kind of configuration store (file, git, database, etc.). This is an exercise left up to you.</p>
</blockquote>
<p>Line #129 of Listing 3 starts the verify process. A Client may verify a JWT it receives from an Authentication server to guard against tampering while in transit. To verify a JWT created using an asymmetric RSA SHA256 signature, the Client will need the <strong>public key</strong>. This typically is not a problem since public keys are designed to be giving away. Line #132 demonstrates the call to <code>.verify()</code>.</p>
<p>That’s it for JWT with Asymmetric RSA SHA256 Signature.</p>
<h1 id="summary">Summary</h1>
<p>Most of this blog is the code. Review the code, top to bottom, it is not overly complicated. But now you know how to create and verify JWT values using both a Symmetric HMAC SHA256 Signature and an Asymmetric RSA SHA256 Signature.</p>
<p>Enjoy!</p>
<h1 id="references">References</h1>
<p><em>Introduction to JSON Web Tokens</em>. (n.d.). JWT. <a href="https://jwt.io/introduction">https://jwt.io/introduction</a>.</p>
<p><em>Debugger</em>. (n.d.). JWT. <a href="https://jwt.io/#debugger-io">https://jwt.io/#debugger-io</a></p>
<p>Alx. (2017, December). <em>Create jwt in java using Public key rsa</em>. <a href="https://wstutorial.com/misc/jwt-java-public-key-rsa.html">https://wstutorial.com/misc/jwt-java-public-key-rsa.html</a></p>
<p><em>Base64 withoutPadding Encoding of string or byte array (Java8)</em>. (n.d.). MakeInJava Tutorials. <a href="https://makeinjava.com/base64-withoutpadding-encoding-string-byte-array-java8/">https://makeinjava.com/base64-withoutpadding-encoding-string-byte-array-java8/</a></p>
<p>Poulsen, Søren. (n.d.). <em>Calculate HMAC-Sha256 with Java</em>. <a href="https://sorenpoulsen.com/calculate-hmac-sha256-with-java">https://sorenpoulsen.com/calculate-hmac-sha256-with-java</a></p>
<p>Dommerholt, Niels. (2016, December 28). <em>Example of RSA generation, sign, verify, encryption, decryption and keystores in Java</em>. <a href="https://gist.github.com/nielsutrecht/855f3bef0cf559d8d23e94e2aecd4ede1">https://gist.github.com/nielsutrecht/855f3bef0cf559d8d23e94e2aecd4ede1</a></p>
<p><em>HMAC</em>. (2023, January 1). Wikipedia. <a href="https://en.wikipedia.org/wiki/HMAC">https://en.wikipedia.org/wiki/HMAC</a></p>
<p><em>Cryptographic hash function</em>. (2023, January 10). Wikipedia. <a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">https://en.wikipedia.org/wiki/Cryptographic_hash_function</a></p>
<p><em>Hash function</em>. (2023, February 1). Wikipedia. <a href="https://en.wikipedia.org/wiki/Hash_function">https://en.wikipedia.org/wiki/Hash_function</a></p>
</body>
</html>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com2tag:blogger.com,1999:blog-383035171837618940.post-85529758755113286312022-12-08T06:17:00.006-06:002022-12-08T08:51:31.429-06:00Automatically Delete Emails in Outlook Based on Age<h1 id="abstract">Abstract</h1>
<p>Do you get a lot of email notifications? I’m particularity thinking of daily notifications from your system which you largely ignore but still fill up your mailbox. This post is a short tutorial on how you can use the AutoArchive feature in Outlook to automatically delete these emails yet keep some recent emails for review.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>Outlook from Microsoft 365</li>
</ul>
<h1 id="introduction">Introduction</h1>
<p>My inbox fills up with “monitoring” emails which are sent by multiple servers every day. Do I review all these emails every day? Nope. Most times I don’t look at them for weeks, the emails build up into the hundreds, and when I do finally review them, I’m only interested in the latest ones. To help manage this, I configured the Outlook AutoArchive feature to automatically delete old emails I’m no longer interested in. Doing this is very easy.</p>
<h1 id="folderproperties">Folder properties</h1>
<p>First, right click on the folder (I’m sure you have already created a rule that moves the emails to a certain folder which you’ve been ignoring) and select properties as shown in figure 1.</p>
<h1 id="figure1-folderproperties">Figure 1 - Folder Properties</h1>
<figure>
<img src="https://bnz06pap001files.storage.live.com/y4mkqByEtTxLi8-FyCmlmdRl6kMcL5uJGJQJMlNmFx5cFLstRzsrJEU6kFjWwvy3ghv6ASia5AAXHba2suoj3U_-9XAwSpiTEGz6j-PPzyAJU3dQ-1plcm5a3NE87jl_udG6GZi25tbhGP_b1vwRTKOCb4fBed3RXYK9ZUJ3x9_qIDuHNoPiq4h5uZwrZY2zraD?width=305&height=468&cropmode=none" alt="Folder Properties" title="Folder Properties" />
<figcaption>Folder Properties</figcaption>
</figure>
<h1 id="autoarchiveconfiguration">AutoArchive Configuration</h1>
<p>Next, select the <code>AutoArchive</code> tab and configure it the way you want. As you can see in figure 2, I have my folder configured to permanently delete emails which are older than 1 week.</p>
<h1 id="figure2-autoarchiveconfiguration">Figure 2 - AutoArchive Configuration</h1>
<figure>
<img src="https://bnz06pap001files.storage.live.com/y4mYA53wxW1R-GquGyzMI9P8Kjh_WZzg7DoF1SSgOffT35Hi-1dyuGDzg-e0Ck-4MD70p1s6aMHSkVvMTKun-gf705MsMsGuzI9NtZ5ZyD0laNlSTh3yrGPVXKsTXkC-WbhfmEY-y9hsuX-fdqAn-D9ix7CWPfTUM8X9V5n1SEmPjMI9hpFoDAO7Ic1k8ArIeaK?width=400&height=548&cropmode=none" alt="AutoArchive Configuration" title="AutoArchive Configuration" />
<figcaption>AutoArchive Configuration</figcaption>
</figure>
<h1 id="summary">Summary</h1>
<p>That’s it. Very simple. Enjoy!</p>mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-16843647031556763222022-11-17T10:06:00.000-06:002022-11-17T10:06:17.022-06:00Pragmatic Strategy for Deconstructing a Monolith into Microservices<h1 id="abstract">Abstract</h1>
<p>This article is a result of more than a year of research and study (2019–2020) into the microservice design pattern, domain driven design (DDD), feature driven design (FDD), hexagonal (ports and adapters) architecture, and event storming. I started studying just microservices but was quickly lead to the other topics because they all seemed to have a synergy with each other. I gained a lot of knowledge during my studies, but was disappointed this knowledge was mostly theoretical. Practical questions like, “What is a domain?” or “How do I deconstruct my monolith?” were hand-waved with the answer, “It depends”. As a software architect, I felt I needed to be able to give better answers.</p>
<p>The purpose of this article is to make an attempt to combine all of these <strong>theoretical</strong> patterns, designs, and architectures with the ultimate goal of creating a <strong>pragmatic strategy for deconstructing a monolith into microservices</strong>. By pragmatic, I mean an actual step-by-step guide that is realistic and actionable that will get you from monolith to microservices.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<p>This post also assumes you are not Google, Facebook, Uber, Spotify, etc, who necessarily have solution patterns which are largely not applicable to most everyday applications.</p>
<h1 id="whyastrategy">Why a Strategy?</h1>
<p>You might be asking yourself, “A pragmatic strategy for deconstructing a monolith? A step-by-step guide that is realistic and actionable?”</p>
<p><em>IMPOSSIBLE!</em></p>
<p>I bet that is what you are thinking. After all, “You don’t know MY monolith!”</p>
<p>That is true. I do not know YOUR monolith, but I know MONOLITHS. I also know that no strategy will ever be 100%. But what if the strategy is 90%? Or even 75%? Personally, I would rather start with some kind of strategy instead of no strategy at all.</p>
<p>Why is a pragmatic strategy for deconstructing a monolith needed? Why not just start coding and see what happens? Let us take a look at a few thoughts about doing this.</p>
<p>First, consider well-known architect Grady Booch’s comments about agile and architecture. Agile (typically scrum) is all the rage and with agile you throw away documentation and design in favor of code, code, code! The common thought is that agile will <em>eventually</em> get you to a good architecture. This is just not true. We all know it is not true, but we continue to do it anyway. Why? Because of the hope that agile may get you a good architecture by accident. What does Grady Booch have to say about this? “Don’t count on it!…A good architecture is <strong>intentional</strong> and must be <strong>thought out</strong> and <strong>planned</strong> by an experienced architect” (Booch, youtube, 2016). The bottom line is this: If your strategy for deconstructing your monolith is to code, code, code and wing it, then good luck!</p>
<p>Next, are the ever-present time and budget questions that must be answered, but how do you answer them? Managers ask, “How long will it take?”, which really translates into, “How much will it cost?” About this, Grady Booch says, if it doesn’t cost much to change, it is design, otherwise it is architecture. (Booch, youtube, 2016). Designing solutions in an existing monolith is cheap (Mas Ruiz, dev.to, 2019). Completely rewriting the monolith as microservices is not cheap. But how much “is not cheap”? To answer this question you will need to know roughly how many microservices you are going to need. Knowing this will allow you to estimate lots of other costs associated with developing, building, and running microservices. How are you going to figure how many microservices you need? To do that, you need some kind of <strong>pragmatic strategy</strong> for deconstructing a monolith. It will not be perfect, but it will get you started with making a reasonable estimate.</p>
<p>Finally, software architects just have to do better! The microservice design pattern is big on concepts, ideas and technology, but small on strategy. In much of my research, DDD and microservices go hand in hand as a “way to do it”. But, consider this very common DDD question: “What are the bounded contexts of my application?” A software architect may respond, “It depends” or “They will eventually emerge”. We must have better answers. We must have some kind of <strong>pragmatic strategy</strong> which enables us to answer questions like these.</p>
<h1 id="thestrategy">The Strategy</h1>
<p>My <strong>pragmatic strategy</strong> for deconstructing a monolith into microservices consists of five realistic and actionable steps:</p>
<p>Step 1. Define “Microservice”</p>
<p>Step 2. Deconstruct the Monolith by Features</p>
<p>Step 3. Understand Bounded Context</p>
<p>Step 4. Define Domains</p>
<p>Step 5. Embrace The Hexagon</p>
<p>That is it. Only five steps! Simple, huh? Let the endless debate begin! Well before it begins, let us at least walk through the steps.</p>
<h1 id="step1.definemicroservice">Step 1. Define “Microservice”</h1>
<p>What is a microservice? This is a loaded question! It needs to be answered before moving forward though. The answer is extremely important because <strong>your understanding of what a microservice is will influence how you decide to implement them</strong>. For example, if you think of a microservice as a <em>layer</em> of your current layered architecture (account data), then you will end up building a distributed monolith. If you think of a microservice as <em>remote methods</em> or <em>utilities</em> (pattern match this data), you will end up building a Death Star. So defining “microservice” is critical.</p>
<p>Of all the definitions I have seen, the one I like the best was re-published on DZone from Maruti Techlabs’s. Figure 1 shows the definition (Makadia, dzone, 2020).</p>
<h1 id="figure1-techlabsmicroservicebestpractices">Figure 1 - Techlab’s Microservice Best Practices</h1>
<figure>
<img src="https://dsm01pap005files.storage.live.com/y4maPQ-IekRLU40MHX60w3RwAjzGvBEMXENNIvhQaPLSC06_gyX6P6DBbd5rgrN2o3Ahjg6vqT3DUboM0hUXwse6x5Hyv57lIMhhXrREbTerQSvvd2F65VSnaijtby7CJT-GpO0LBivMyDPKvZTEza1W_jyCGBTToy0Sk6gQOMWeZpQoDOQ-m11LSf5CYOltG_8?width=1000&height=1173&cropmode=none" alt="Techlab's Microservice Best Practices" title="Techlab's Microservice Best Practices" />
<figcaption>Techlab’s Microservice Best Practices</figcaption>
</figure>
<p>From figure 1, I derive my definition for “microservice”:</p>
<p><strong>A microservice is a stand-alone, self-contained <em>product</em> that carries out a single important business <em>feature</em>.</strong></p>
<p>This definition is my starting point for my <strong>pragmatic strategy</strong>. I specifically use the words “product” and “feature” for reasons you will discover as you continue reading. Stating that a microservice is something that is responsible for providing a business feature implies that it is something more than just a <em>layer</em>, <em>method</em> or <em>utility</em>.</p>
<p>A microservice is more than just a <em>layer</em> of your current architecture. Yes, a <em>layer</em> of your code handles account data, but this by itself is not a complete business feature. It may be something needed to implement a feature, but it is not a stand-alone, self-contained product by itself.</p>
<p>A microservice is more than just a <em>method</em> or <em>utility</em> of your current code base. Yes, there is some wonderful code to parse and pattern match data, but, this is not a stand-alone, self-contained business feature by itself either.</p>
<p>So <strong>a microservice is a stand-alone, self-contained product that carries out a single important business feature</strong>. This definition is not only clear and concise, but it also lines up perfectly with the well-known characteristics of microservices. Figure 2 shows these characteristics (Ma, medium, 2018).</p>
<h1 id="figure2-characteristicsofmicroservices">Figure 2 - Characteristics of Microservices</h1>
<figure>
<img src="https://dsm01pap005files.storage.live.com/y4mjNE1Ns06J47uwf0mTkj1LHC5y_o_1jxwSOvgwiWFmjRkUJYLwKtJn-ahk34idwc-hCfWnluuBC8oy6WNNs0MWuE3ALZSTpJJO-UT6IhC7XSGs9kotueBjLezMV3slw9hDxDilHjdRnm5Koqmfo-aYiDKW-WSD7qEMqhZ0zMU-RvW1JOtbN489-9rxRtLjpEp?width=3641&height=1545&cropmode=none" alt="Techlab's Microservice Best Practices" title="Characteristics of Microservices" />
<figcaption>Techlab’s Microservice Best Practices</figcaption>
</figure>
<p>Let us see how these characteristics line up with my microservice definition.</p>
<ol>
<li><strong>Single purpose</strong> is building a product which carries out a single important business feature, not twenty different things.</li>
<li><strong>Loose coupling</strong> is the degree of interdependence between software modules (Pagade, 2021). Being stand-alone means not needing a dozen other things (other microservices) up and running in order to work successfully.</li>
<li><strong>High cohesion</strong> is the degree to which the elements inside a module belong together (Pagade, 2021). Being self-contained means that in one place (typically a single source code repository or project) you find everything you need to update when the business feature requires a change.</li>
</ol>
<p>Although this is a great definition for microservice, there is still more work to do. This definition depends on knowing what the <em>important business features</em> are. Do you know what they are for you monolith? Next, we will take a look at how to really determine what the business features are of your monolith.</p>
<h1 id="step2.deconstructthemonolithbyfeatures">Step 2. Deconstruct the Monolith by Features</h1>
<p>Now that we have defined <strong>a microservice as a stand-alone, self-contained product that carries out a single important business feature</strong>, how do you determine your monolith’s <em>business features</em>? Answering this question is the very heart of deconstructing the monolith.</p>
<p>To help answer this question, I turn to the blog <em>To Domain Driven Design</em> by Kevin Mas Ruiz. In his blog, he discusses how important it is to stop thinking of your code as a single application (monolith) but instead to think of it as a <strong>platform of independent products</strong>. He writes, “…we need to think [of] our business as a business platform: we don’t have a product, we have a set of products. Those products are a set of features that apply to a persona.” (Mas Ruiz, dev.to, 2019).</p>
<p>Did you see the word <em>feature</em>? This is promising because we are trying to figure out some way of generating a list of <em>business features</em> of the monolith.</p>
<p>To further illustrate how to think about your application as a <strong>platform of independent products</strong> instead of a monolith, Mas Ruiz writes, “For example, based on this pattern, we can define our shopping platform like in the following diagram:”</p>
<h1 id="figure3-shoppingplatform">Figure 3 - Shopping Platform</h1>
<figure>
<img src="https://dsm01pap005files.storage.live.com/y4mEks2iUfQsJwJpybLvEyLH0zwxWXE8Smqn2iHzOCeMhvFOusJEWZROp_e9Db8iHNMI4kHoX7grxuPw-KyF3oU2dyReu7kUKgYhA7ihPh796jydcShvhQLMGo5E2re7TymJu93Wzb9QVIh8X1L5MOBBLQF1_XdKaflcNA2f5-jM9u2iTu4B-5VDarp_44ANSzj?width=880&height=893&cropmode=none" alt="Shopping Platform" title="Shopping Platform" />
<figcaption>Shopping Platform</figcaption>
</figure>
<p>In figure 3 above, Mas Ruiz uses the green stickies to represent what he calls a product of the shopping platform. Since each product contains one or more features, the green stickies also represent the features of the shopping platform.</p>
<p>What is interesting about Mas Ruiz using green stickies is that green stickies are also used in Event Storming (you starting to see the synergy of these concepts?). In Event Storming, green stickies represent a view (Brandolini, 2018). A view is typically a webpage, but it can represent any kind of user interface of an application. Mas Ruiz uses green stickies to represent a product/feature of the platform and Brandolini uses green stickies to represent a user interface of an application. Combining these ideas together leads to the following:</p>
<blockquote>
<p>green stickie == business feature == webpage</p>
</blockquote>
<p>This equivalency is important because it hints at a way to determine your monolith’s business features. Try this…</p>
<p>Login to your monolith. As you click through it, create a list of every webpage with its name/title/description. Supplement with a screenshot if you want. Continue until you have visited every webpage of your monolith. Once you are done…</p>
<p><strong>Congratulations!</strong> You have just finished deconstructing your monolith into microservices.</p>
<p><em>WAIT!</em></p>
<p><em>WHAT?</em></p>
<p>You probably think I have lost my mind. Maybe. Remember my <strong>pragmatic strategy</strong> started with a definition for <em>microservice</em>. Let us revisit the definition.</p>
<p><strong>A microservice is a stand-alone, self-contained product that carries out a single important business feature.</strong></p>
<p>In a monolith, each webpage is (typically) created for a specific reason. In other words, each webpage carries out a single important business feature. Therefore, we can extend the equivalency above to include microservice:</p>
<blockquote>
<p>green stickie == business feature == webpage == microservice</p>
</blockquote>
<p>This equivalency basically means a monolith can be deconstructed into microservices by simply implementing each webpage as its own stand-alone, self-contained product. I can already hear you <strong>screaming</strong> objections! That is OK. I understand that deconstructing a monolith so each webpage becomes its own product will immediately raise a LOT of objections. However, as Mas Ruiz has suggested, we need to stop thinking of our application as a single solution (monolith) and instead think of the application as a platform of independent products which all work together to provide a solution. I hope that my reasoning for deconstructing a monolith so each page becomes a product of the platform solution has some validity.</p>
<p>Remember, my purpose is to attempt to create a <strong>pragmatic strategy</strong> for deconstructing a monolith. No strategy will EVER be 100%, and that is OK. Starting with some strategy is always better than starting with no strategy at all. May it be the case that multiple webpages are group together into a microservice? Of course! But for a strategy, you need to start somewhere.</p>
<p>If you are still with me, congratulations! I know it is not easy and you are probably hesitant. Now that you know each webpage of your monolith will become a microservice, the next thing we want to look at is how this relates to DDD bounded contexts.</p>
<h1 id="step3.understandboundedcontext">Step 3. Understand Bounded Context</h1>
<p>Domain Driven Design is…interesting to say the least. Lots of theory, little practice. I want to discuss DDD as part of my <strong>pragmatic strategy</strong> because if you research “how to implement microservices”, you will find suggestions that DDD bounded contexts can be used to define a microservice. The idea is that if you know what a “bounded context” is, then you know how to define - and therefore implement - a microservice. At least that is the theory.</p>
<p>Let us start by defining a DDD bounded context.</p>
<p>A bounded context is a boundary inside of which lives multiple components of a software model. A component has a specific meaning and behavior within the boundary. Behavior within a bounded context is idempotent and/or ACID. A component with the same name - but with different meaning and behavior - must exist in its own boundary. (Vernon, 2016, p. 327)</p>
<p>Given this definition for a DDD bounded context, recall Step #2 of my <strong>pragmatic strategy</strong> that I made this radically controversial equivalency:</p>
<blockquote>
<p>business feature == webpage == microservice</p>
</blockquote>
<p>This equivalency basically means a monolith can be deconstructed into microservices by simply implementing each webpage as its own stand-alone, self-contained product. Now let us propose that a DDD bounded context also defines a microservice. If this is true, it would mean bounded context can be added to the equivalency like this:</p>
<blockquote>
<p>webpage == business feature == microservice == bounded context</p>
</blockquote>
<p>However, is this equivalency true? Can bounded context just be thrown in there? The answer is Yes. Let us break down the definition of bounded context to see why.</p>
<p>A bounded context contains software model components that have very specific meaning inside that bounded context. In the DDD world, this <em>specific meaning</em> is how you create a ubiquitous language (UL). UL is the most important concept in DDD. Everything in DDD is built on UL. UL is intended to define exact meanings for words so that when you use those words everyone understands what you are talking about. Let me illustrate with an example.</p>
<p>Suppose you get the following request from a security auditor: “Please provide a list of all the log files on the server”.</p>
<p><em>WAIT A MINUTE!</em></p>
<p>A server can have dozens of different log files for dozens of different services running on the server. Plus, there are lots of different servers performing different tasks all throughout your environment. So what log files is the security auditor looking for? For what services? For what servers? These questions demonstrate the need for a UL within a context. The problem is not with the auditor’s request. The problem is that for the context of the request - security audit - the meanings of the words “server” and “log file” have not been adequately defined. In order to respond to the auditor’s request, the team must first get a meeting with the auditor, ask a bunch of questions, and determine what exactly the auditor means by “server” and “log files”. At the meeting, the team learns the auditor is interested in remote SSH connections to the VM running the database. Now the words “server” and “log files” have a very specific meanings. A UL for the context - security audit - has been established so everyone can understand each other when the words “server” and “log files” are used.</p>
<p>Now suppose there is a production problem with the application. In order to troubleshoot, the team starts a conversation about getting the “log files on the server”. Does the team confuse this conversation with the security audit conversation? No, of course not! But why not? After all, the same words, “server” and “log files”, are being used. The team does not get confused because there are two different contexts: security audit and production problem. Within each context, “server” and “log files” have specific and different meanings. Bringing this back to DDD, “security audit” and “production problem” are bounded contexts and “server” and “log files” are software model components.</p>
<p>Now that we have a better understanding of bounded context, how does it relate to my <strong>pragmatic strategy</strong>? To answer this question, let us look at a common data-related objection to my strategy. The objection goes something like this:</p>
<blockquote>
<p>20 different webpages of the monolith use policy data, so they should all be in the same microservice, right? But if I put 20 webpages in the same microservice, that gets me right back to building a monolith. So I think I will need to extract <em>policy</em> as its own microservice. But <em>policy</em> is not a stand-alone, self-contained business feature by itself, so if I extract it on its own then I will start to have tight coupling between microservices. Oh boy!</p>
</blockquote>
<p>Mas Ruiz addresses this objection when discussing cross-product modules. He gives the illustration shown in figure 4. The “1 click purchase” feature seems to need the same data as the “Purchase” feature (Mas Ruiz, dev.to, 2019). If that is the case, then it would seem both features should collapse together instead of being independent. But before doing that, Mas Ruiz says to dig deeper.</p>
<h1 id="figure4-cross-productmodules">Figure 4 - Cross-Product Modules</h1>
<figure>
<img src="https://dsm01pap005files.storage.live.com/y4moaTHJ-ifYnlgeNKV31toZWuozRx0ntyHZs26UrfsmCpeu6IZqqxnCtatr1tLD8lLWe4LdXd3zjOt26-dclBUl87bpsz1nlZdMxZ2rj3kXXZeo3ilsTIjgxgvg1YL7IjZbmbtcb8InlK2eOeAAK6bXh_IkSaYig-yKRkwtHws_G3z2xYgvFI6A-Uk7KrL6zXi?width=512&height=569&cropmode=none" alt="Cross-Product Modules" title="Cross-Product Modules" />
<figcaption>Cross-Product Modules</figcaption>
</figure>
<p>Mas Ruiz uses the word “modules” to describe the orange boxes in figure 4. These orange boxes represent the data needed for both “1 click purchase” and “Purchase”. Notice the words in the orange boxes are identical!</p>
<blockquote>
<p><strong>NOTE</strong>
Do not get these orange boxes mixed up with Event Storming orange stickies. In this case, there is no similarity between the two.</p>
</blockquote>
<p><em>BUT WAIT!</em></p>
<p>We have seen this <em>identical word</em> problem before when breaking down the definition of bounded context…remember the “server” and “log files” example above? Yes, in figure 4, the words “buyers”, “books”, “stock”, and “shipment” are used for both the “1 click purchase” feature and the “Purchase” feature. But Mas Ruiz’s point is if you dig deeper into them you will discover the views of the data for each feature will be different. So within the “1 click purchase” feature, “books” has one meaning and in the “Purchase” context, “books” has a different meaning. This is the whole point of DDD bounded contexts.</p>
<p>If you think about it, this should not be surprising. Each webpage is created to carry out a specific business feature. Each business feature has a different (if somewhat subtle) view of the data and operations on the data. In fact, if the view and operations were exactly the same, you would have multiple pages performing the exact same business feature!</p>
<blockquote>
<p><strong>NOTE</strong>
This is not outside the realm of possibility. Over time, you will lose both the development and business teams that first created the application. Knowledge is lost, so features may be forgotten and re-implemented.</p>
</blockquote>
<p>So is this equivalency true?</p>
<blockquote>
<p>webpage == business feature == microservice == bounded context</p>
</blockquote>
<p>The answer is yes! A webpage is created to perform some specific business feature. Within the context of this business feature, the view and operations on the data has a very specific meaning which does not exist on any other webpage. Therefore, it can and should be implemented as a stand-alone, self-contained product as my microservice definition suggests.</p>
<blockquote>
<p><strong>NOTE</strong>
If you think back to the 2000 year time frame when service-oriented architecture (SOA) was all the rage, you can now understand why that concept failed so miserably. SOA set out to be the one-definition-to-rule-them-all so to speak. If you needed policy information, SOA tried to centrally define “Policy” for the ENTIRE organization. DDD bounded contexts tell us you just can not do that!</p>
</blockquote>
<p><em>BUT WAIT!</em></p>
<p>Without a doubt, different webpages will need to use similar, but not identical, data. If each webpage is its own microservice and is developed as a stand-alone, self-contained, independently-deployable product, how does each microservice get the data it needs?</p>
<p>Great question! For possible answers, go to <a href="https://microservices.io/" title="Micoservices.io">Micoservices.io</a> and on the homepage search for “data management”. On <a href="https://microservices.io/" title="Micoservices.io">Micoservices.io</a>, Chris Richardson documents a number of different strategies for how to handle data and databases. Spoiler alert, it is not easy! This is especially true when considering ACID requirements of business operations. I specifically say “business operations” here instead of transactions because the requirement should be that when the button is clicked, everything gets done. It does not matter if it is transactional or not, just as long as everything gets done.</p>
<p>My recommendation for data management is the <a href="https://microservices.io/patterns/data/shared-database.html" title="Shared Database Pattern">shared database pattern</a> (Richardson, n.d., Pattern: Shared database). For implementation, keep microservices isolated to a database schema created just for that microservice. The data in the schema is handled in various ways including:</p>
<ul>
<li><strong>TABLE</strong>. Data is owned by that schema (microservice).</li>
<li><strong>VIEW & MATERIALIZED VIEW</strong>. Read-only sharing of data from other schemas (microservices).</li>
<li><strong>TRIGGER</strong> Read-only copies of data from other schemas (microservices).</li>
</ul>
<p>Handling data is a very, very complicated topic unto itself. For microservices, the one-database-per-microservice diagram is what everyone loves to see and therefore what everyone strives to implement. But, given its massive complications, is it worth it? (Richardson, n.d. Virtual bootcamp:…). Like my post disclaimer says, unless you are Google, Facebook, Uber, etc., probably not! I highly recommend you research data strategies on your own.</p>
<h1 id="step4.definedomains">Step 4. Define Domains</h1>
<p>The next thing to do in this <strong>pragmatic strategy</strong> is to define domains. For DDD, Defining domains would seem to be a very important because, after all, it is in the name: <em>Domain</em> Driven Design. I think the bounded context is a much more important DDD concept, but domains have value too.</p>
<p>Despite the word “domain” being part of the name <em>Domain</em> Driven Design, a domain is actually one of the most confusing and least explained concepts of DDD. In <em>Domain-Driven Design Distilled</em>, Vernon does not give a definition of domain. The best we get is a discussion about something called a core domain.</p>
<blockquote>
<p>“When compared with all the software your organization uses, a Core Domain is a software model that ranks among the most important, because it is a means to achieve greatness. A Core Domain is developed to distinguish your organization competitively from all others. At the very least it addresses a major line of business. Your organization can’t excel at everything and shouldn’t even try. So you choose wisely what should be part of your Core Domain and what should not. This is the primary value proposition of DDD, and you want to invest appropriately by committing your best resources to a Core Domain.” (Vernon, 2016, pg. 13).</p>
</blockquote>
<p>This really does not define domain. It gets even more confusing because in addition to domains there are also subdomains. The words “domain” and “subdomain” seem to be used interchangeably adding even more confusion. For example, Vernon lists three types of subdomains:</p>
<blockquote>
<p>Types of Subdomains</p>
<ol>
<li>Core Domain</li>
<li>Supporting Subdomain</li>
<li>Generic Subdomain
(Vernon, 2016, pg. 46)</li>
</ol>
</blockquote>
<p>Now wait just a minute. “Core Domain” is a type of subdomain? That does not make any sense. “Core Domain” should be a domain right? It is in the name! So why is it listed as a type of subdomain? If it is a type of subdomain, then why is it not called “Core Subdomain”? If this is not confusing enough, an attempt is made to establish a relationship between domain/subdomain and bounded context. You can imagine how well this goes.</p>
<p>For my <strong>pragmatic strategy</strong>, I ignore all of the DDD book treatments of domain and subdomain because they just do not work or make any sense.</p>
<p>My definition of domain:</p>
<p><strong>A domain is a high-level, one- or two-word description that can be used to logically group together the stand-alone, self-contained products that carry out the important business features of the application platform.</strong></p>
<p>That is it. Very simple. Here are examples of domains I have encountered a lot:</p>
<ul>
<li>Reporting</li>
<li>Configuration</li>
<li>Administration</li>
<li>Health Check</li>
<li>Performance</li>
</ul>
<p>Get the idea? If your application gets a lot of data from 3rd parties, you may have a <em>Data Processing</em> domain. If your application converts files into different formats, you may have a <em>Converter</em> domain. If your application processes orders, you may have an <em>Order Processing</em> domain.</p>
<p>An application should have a relatively few number of domains meaning that all of the features of the application should logically fit into relatively few categories. If you find yourself writing down dozens of different domains, then either one of two things are happening.</p>
<ol>
<li>Your monolith is big hodgepodge of a lot of different responsibilities. This is very telling!</li>
<li>What you are writing down is not high-level enough. If you have <em>ABC File Import</em>, <em>XYZ File Processing</em>, and <em>123 Data Validation</em> as domains, all three of these can probably be assigned to a <em>Data Processing</em> domain.</li>
</ol>
<p>Try this. Remember that list of webpages from Step #2 of my <strong>pragmatic strategy</strong>? For each one, assign it a one- or two-word, high-level category. Do this for every line of the list.</p>
<p><strong>Congratulations!</strong> You just assigned a domain to every product of your application platform!</p>
<p><em>NOW WHAT?</em></p>
<p>There is some value in assigning domains. Domains can help make return on investment (ROI) decisions. Take the <em>Reporting</em> domain for example. Would it be better for the software developers to code custom reports into the application? Or, would it be better to purchase a reporting tool and allow users to create whatever reports they want? This is an ROI decision. If the primary purpose of your application is reporting on extremely complicated data, then it is best for the software developers to code them. If the primary purpose of your application is something else, then buying a reporting tool to free up developers' time might be a better ROI. If you find yourself writing down dozens of different domains, that may indicate you application is performing a lot of unrelated business functions and maybe some of them are the responsibility of other teams? Or maybe new teams need to be formed? Again, ROI decisions for the organization.</p>
<p>Overall, the domain concept is not difficult as long as you avoid the DDD book treatments and stick with my definition.</p>
<h1 id="step5.embracethehexagon">Step 5. Embrace the Hexagon</h1>
<p>The final step of my <strong>pragmatic strategy</strong> is to embrace the Hexagon. What in the world does this mean?</p>
<p>If you go through Steps 1–4, you will get a good idea of what your application will look like - on paper - rewritten from monolith to microservices. The paper can be used for project planning, budgeting, time estimates, event storming, and lots of other things. However, what you do not have yet is code. Remember, “A good architecture is <strong>intentional</strong> and must be <strong>thought out</strong> and <strong>planned</strong> by an experienced architect” (Booch, youtube, 2016). Now that you have done this, software developers will eventually need to start writing code. The Hexagon will help make sure code is organized properly.</p>
<h1 id="figure5-oreillyplanfirstcodelatersticker">Figure 5 - O’Reilly Plan First Code Later Sticker</h1>
<figure>
<img src="https://dsm01pap005files.storage.live.com/y4mpTko3JpGuEMSSTZzSwJs7EF7suLlbIm4pRHcEq-szYxxOQMJxpZj6rcbHa8OPZTuwG8fa3ZJlZbg6rn2D8r6JatvqKqbWgpaK-ugjiZVkXkj3bnd25wo4YVo0glQ3m_RnIwSv7BeeP3Nvp8PLUyykYMKHZ0vLpPgqqTtKZpxNnstjl85joAG65SaAnf7nzV6?width=1416&height=1516&cropmode=none" alt="Cross-Product Modules" title="O'Reilly Plan First Code Later Sticker" />
<figcaption>Cross-Product Modules</figcaption>
</figure>
<p>Alistair Cockburn documented the hexagonal (ports and adapters) architecture in a work published in 2005. Cockburn stated the intent of the architecture is to “Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.” (Cockburn, 2005). The hexagonal layers of the architecture allows code to be organized and written to adhere to the intent of the architecture. Figure 6 gives an overview of the layers.</p>
<h1 id="figure6-hexagonalarchitecturelayers">Figure 6 - Hexagonal Architecture Layers</h1>
<figure>
<img src="https://dsm01pap005files.storage.live.com/y4mlgoYkvXntF2jAQPyt3aeJPXSD9zBC3848OWVouqNj8HEU8RpxmlF8b8yerYcBzEl_kJotQPZNqvR8odwsT83zFoGWPWevLjDZVw4J3ryqepJ85KE44_LT3r7LXX0hzrpfL2g0sp6sd5h53wUWFG5qBabTFN22-Sp5GtEc9KaOoG3AJUoJqSawoC2jJ_oyHXa?width=792&height=870&cropmode=none" alt="Cross-Product Modules" title="Hexagonal Architecture Layers" />
<figcaption>Cross-Product Modules</figcaption>
</figure>
<p>Hexagonal Architecture is a huge topic all by itself. I encourage you to research it more on you own. Here is a high-level summary, describing the purpose of each layer.</p>
<h2 id="ui">UI</h2>
<p>The UI is not actually one of the layers, it is everything outside of the hexagon. It is called UI because it is common for some kind of user interface to want to interact with your application. However, as shown in figure 6, there are lots of other ways of interacting with your application, such as HTTP, JMS, SMTP, and SFTP. Each interaction has a unique protocol it uses to communicate with your application. When communication does occur, it happens with the outer most hexagon layer, the framework layer.</p>
<h2 id="frameworklayer">Framework Layer</h2>
<p>The Framework layer is responsible for:</p>
<ol>
<li>Accepting a raw driver request through some communication protocol.</li>
<li>Converting a raw request into an <em>application specific request</em>, then sending this <em>application specific request</em> down to the next layer, the Application layer.</li>
<li>Converting the <em>application specific response</em> into a raw driver response and sending the response back through the communication protocol.</li>
</ol>
<p>Typically the Framework layer is the smallest of the layers since raw driver requests are simply converted and passed to the application layer for handling. The Framework layer does not contain any business code. The Framework layer communicates with the Application layer through an <em>application primary port</em>. This is simply a fancy way of saying an instance of an Application layer class is injected into an instance of a Framework layer class.</p>
<p>A Spring <code>@RestController</code> is a perfect example of a Framework layer class. Its primary responsibility is to accept a raw HTTP protocol request, do something with it, then send a raw HTTP protocol response back. The “do something with it” part is handled by an Application layer class that gets created and injected into the <code>@RestController</code> by Spring. Next, we will look at these Application layer classes in more detail.</p>
<h2 id="applicationlayer">Application Layer</h2>
<p>The Application layer is the hardest layer to understand because of its unique position between the Framework layer and the Domain layer. The Application layer is responsible for:</p>
<ol>
<li>Converting an <em>application specific request</em> into an orchestration of domain operations performed by the Domain layer.</li>
<li>Providing the Domain layer concrete implementations of the exit points out of the Domain layer.</li>
<li>Converting the domain response into an <em>application specific response</em>, sending the response back up to the Framework layer.</li>
</ol>
<p>Let us take a look at each of these responsibilities in more detail.</p>
<p>First, the Application layer takes an <em>application specific request</em> and converts it into an orchestration of domain operations performed by the Domain layer. Like the Framework layer, the Application layer does not contain any business code. But the Application layer does know what Domain layer operations need to be executed to fulfill the <em>application specific request</em>. The Application layer communicates with the Domain layer through a <em>domain primary port</em>. This is simply a fancy way of saying an instance of a Domain layer class is injected into an instance of an Application layer class.</p>
<p>Second, the Application layer is responsible for providing the Domain layer with concrete implementations of the exit points out of Domain layer. What are exit points? Well, at some point, the code in the Domain layer is going to need to communicate with the outside world. Databases are the most well known example of this kind of communication. Other examples are message brokers, email, and files. Suppose code in the Domain layer needs to retrieve and store data. This is an exit point. Though the Domain layer needs to retrieve and store data, it <strong>does not need to know how</strong> the data is retrieved and stored. Because it does not need to know <em>the how</em>, those operations are defined by interfaces only. It is then the responsibility of the Application layer to provide concrete implementations of these interfaces so that when a dependency injection engine (CDI, Spring, etc.) wires everything together, the data is retrieved and stored in a manner that best suites the application. The interfaces in the Domain layer are the <em>domain secondary ports</em> and the classes in the Application layer that implement these interfaces are the <em>application secondary adapters</em>.</p>
<p>Third, any responses from the Domain layer are converted into an <em>application specific response</em> and passed back up to the Framework layer. Remember the Framework layer then takes this <em>application specific response</em> and converts it into a raw driver response and sends the response back through the communication protocol.</p>
<h2 id="domainlayer">Domain Layer</h2>
<p>Finally is the inner most layer, the Domain layer. This is the most important layer because it is finally in this layer where all of the business code is located. The Framework layer and Application layer have specific responsibilities, but are basically pass throughs to the Domain layer.</p>
<p>Being protected by the other layers makes the code in the Domain layer very easily reusable. The code has no knowledge of the HTTP protocol or how to talk to a database. So as your application evolves, the code in the Domain layer should easily evolve with it. And that is what you want. For example, suppose you are changing from an N-Tier architecture based on Struts to a microservice architecture based on Angular and Spring boot. The Domain layer, with all the business code, should not care. It should be equally reusable in both applications. If the architecture change reveals the need for code in the Domain layer, the code in the Domain layer was not created correctly. Similarly, if the application’s architecture further evolves into use cloud-native services (like AWS Lambda), again, the Domain layer, with all the business code, should be reusable without any changes. This is a tall order and takes discipline on the software development team to achieve. Knowing the responsibilities of each hexagon layer helps achieve it.</p>
<p>Being protected by the other layers makes the code in the Domain layer very easily testable. Because interfaces are used to define exit point operations, unit test frameworks need only to mock these interfaces with suitable responses for each test. If unit tests require the startup of a complex testing runtime to simulate a database or other external resources, the unit tests are wrong. Keep the focus on testing the business code, not simulating a runtime environment. If unit tests are written this way, it fulfills the primary intent of the hexagonal architecture, which is to “Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.” (Cockburn, 2005).</p>
<p>This was a very quick and very high-level review of hexagonal architecture. Again, I encourage you to research it more on your own. Now that you understand the purpose of each layer, we can translate this into code.</p>
<h2 id="code">Code</h2>
<p>When the team starts coding, the first thing to do is create a source code repository for each microservice. Remember, <strong>a microservice is a stand-alone, self-contained product that carries out a single important business feature</strong>, so the code for a microservice should exist in its own source code repository. Suppose you need to code the <em>ABC Reconciliation Report</em> business feature. Create a source code repository with the following name:</p>
<pre><code class="text">abc-reconciliation-report
</code></pre>
<p>Now that you have a repository, what do you put in it? Follow The Hexagon and create projects based on the layers. Physically these are file system folders created inside the repository. Logically these folders are referred to by different names depending on the technology you are using. If you are using Java and Maven, you may refer to these folders as modules of a multi-module Maven project. If you are using C#, you may refer to these folders as projects of a solution. For simplicity, I will refer to them as projects. What you name the projects identifies the layer of The Hexagon the code is for and also declares the intent of the code. For example, you may have projects which look like this:</p>
<pre><code class="text">abc-reconciliation-report
/api
/jms
/application
/domain
/ui
</code></pre>
<p><strong>/api</strong> A hexagon <strong>Framework layer</strong> module containing REST endpoints. Provides a way the outside world may interact with the domain. Responsible for translating HTTP(S) requests into application layer objects for processing.</p>
<p><strong>/jms</strong> A hexagon <strong>Framework layer</strong> module containing messaging (JMS) listeners. Provides a way the outside world may interact with the domain. Responsible for translating raw JMS messages into application layer objects for processing.</p>
<blockquote>
<p><strong>NOTE</strong>
You may be asking, “Why not name the project ‘framework’ and put all Framework layer code in one project?” Recall from figure 6, There may be many different <em>UI</em> components that want to communicate with your application and each component may use a different protocol for communication. You want to separate the protocol handlers into different projects. So a project for handling HTTP REST communication should be separate from the project handling JMS message communication. Following The Hexagon, each project can re-use the code from the Application and Domain layers.</p>
</blockquote>
<p><strong>/application</strong> A hexagon <strong>Application layer</strong> module containing domain orchestration and domain implementations. These implementations provide a way the domain may interact with the outside world (exit points).</p>
<p><strong>/domain</strong> A hexagon <strong>Domain layer</strong> containing the important business code. This layer should have no knowledge of the Framework or Application layer code. It should be easily testable without any external runtime environments and easily reusable as Framework or Application layer code evolves.</p>
<p><strong>/ui</strong> A UI compoent. Responsible for translating user input into HTTP(S) - typically - to interact with /api for requests.</p>
<p>As you can tell by the the names and responsibilities of these projects, everything for the <em>abc-reconciliation-report</em> is contained in a single repository. This really helps the code adhere to the characteristics of a microservice. Let us revisit these characteristics (Ma, medium, 2018) and see how well code code matches up.</p>
<ol>
<li><strong>Single purpose</strong>. The name of the repository is <em>abc-reconciliation-report</em>. I think that pretty much defines its purpose. If the code is doing anything other than this, the software developers got the code wrong.</li>
<li><strong>Loose coupling</strong>. Hopefully, the Application layer code which implements the Domain layer exit points keeps coupling loose. If not, the software developers got the code wrong.</li>
<li><strong>High cohesion</strong>. If you need to make a change to the ABC Reconciliation Report, it should be clear that this repository is the only place you need to go. If not, the software developers got the code wrong.</li>
</ol>
<h1 id="summary">Summary</h1>
<p>That is it! As a software architect, you can use steps 1–4 of my <strong>pragmatic strategy</strong> to plan the architectural shift from monolith to microservices. As a software developer, you can use step 5 to organize the source code repositories and the contents of each repository and get coding!</p>
<p>It has been a journey, right? Here is a summary.</p>
<p>I wanted to learn how to deconstruct a monolith into microservices. To learn this, I studied microservice design pattern, domain driven design (DDD), feature driven design (FDD), hexagonal (ports and adapters) architecture, and event storming. After a year, I learned a lot of theory but little practice. So I decided to create my own <strong>pragmatic strategy</strong> for deconstructing a monolith into microservices; A step-by-step guide that is realistic and actionable.</p>
<p>My strategy is implemented with five steps:</p>
<p><strong>Step 1. Define “Microservice”</strong> A microservice is a stand-alone, self-contained product that carries out a single important business feature.</p>
<p><strong>Step 2. Deconstruct the Monolith by Features</strong></p>
<blockquote>
<p>business feature == webpage == microservice.</p>
</blockquote>
<p>Click through your monolith and create a list of every webpage. Congratulations! You have just finished deconstructing your monolith into microservices</p>
<p><strong>Step 3. Understand Bounded Context</strong></p>
<blockquote>
<p>webpage == business feature == microservice == bounded context.</p>
</blockquote>
<p>A webpage defines a bounded context because the data and behavior on that webpage is unique and not found on other webpages.</p>
<p><strong>Step 4. Define Domains</strong> A domain is a high-level, one- or two-word description that can be used to logically group together the stand-alone, self-contained products that carry out the important business features of the application platform (microservices). Go get the list of all the webpages of your application. For each line, write down a one- or two-word, high-level description of the product. You have just assigned a domain to every product of your application platform. Use domains to make ROI decisions.</p>
<p><strong>Step 5. Embrace The Hexagon</strong> Use the concepts from hexagonal architecture to organize the source code repositories and the contents of each repository so that software developers know which code goes in which projects so code with similar responsibilities stay together in the same layer.</p>
<p>That is it. Get started. Have fun. Remember no strategy is 100%, so be pragmatic, but also be flexible.</p>
<h1 id="references">References</h1>
<p>Booch, Grady. (2016, June 30). <em>SATURN 2016 Keynote: Architecting The Unknown with Grady Booch [VIDEO]</em>. <a href="https://www.youtube.com/watch?v=RJ3v5cSNcB8&t=2685s">https://www.youtube.com/watch?v=RJ3v5cSNcB8&t=2685s</a>.</p>
<p>Makadia, Mitul. (2020, February, 26). <em>Break a Monolith to Microservices — 12 Best Practices and Design Principles</em>. <a href="https://dzone.com/articles/break-a-monolith-to-microservices-12-best-practice">https://dzone.com/articles/break-a-monolith-to-microservices-12-best-practice</a>.</p>
<p>Mas Ruiz, Kevin. (2019, December 2). <em>To Domain Driven Design</em>. <a href="https://dev.to/kmruiz/to-domain-driven-design-6ao">https://dev.to/kmruiz/to-domain-driven-design-6ao</a>.</p>
<p>Brandolini, Alberto. (2018, November 7). <em>50,000 Orange Stickies Later - Alberto Brandolini - GOTO 2018</em>. <a href="https://www.youtube.com/watch?v=NGXl1D-KwRI&ab_channel=GOTOConferences">https://www.youtube.com/watch?v=NGXl1D-KwRI&ab_channel=GOTOConferences</a>.</p>
<p>Ma, Xiao. (2018, October 17). <em>Microservice Architecture at Medium</em>. <a href="https://medium.engineering/microservice-architecture-at-medium-9c33805eb74f">https://medium.engineering/microservice-architecture-at-medium-9c33805eb74f</a>.</p>
<p>Fowler, Martin (2011, July 14). <em>CQRS</em>. <a href="https://martinfowler.com/bliki/CQRS.html">https://martinfowler.com/bliki/CQRS.html</a>.</p>
<p>Cockburn, Alistair. (2005, April 01). <em>Hexagonal architecture</em>. alistair.cockburn.us. <a href="https://alistair.cockburn.us/hexagonal-architecture/">https://alistair.cockburn.us/hexagonal-architecture/</a>.</p>
<p>Vernon, Vaughn. (2016). <em>Domain-Driven Design Distilled</em>. Addison-Wesley</p>
<p>Richardson, Chris. (n.d.). <em>Microservice Architecture. Microservices.io</em>. <a href="https://microservices.io/">https://microservices.io/</a>.</p>
<p>Richardson, Chris. (n.d.). <em>Pattern: Shared database</em>. <a href="https://microservices.io/patterns/data/shared-database.html">https://microservices.io/patterns/data/shared-database.html</a>.</p>
<p>Richardson, Chris (n.d.). <em>Virtual bootcamp: Distributed data patterns in a Microservice architecture</em>. <a href="https://www.chrisrichardson.net/virtual-bootcamp-distributed-data-management.html">https://www.chrisrichardson.net/virtual-bootcamp-distributed-data-management.html</a>.</p>
<p>Pagade, Ganesh. (2021, May 25). <em>Difference Between Cohesion and Coupling</em>. <a href="https://www.baeldung.com/cs/cohesion-vs-coupling">https://www.baeldung.com/cs/cohesion-vs-coupling</a>.</p>
<p>Cockburn, Alistair. (2005, January 4). <em>Hexagonal architecture</em>. <a href="https://alistair.cockburn.us/hexagonal-architecture/">https://alistair.cockburn.us/hexagonal-architecture/</a>.</p>mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-30167667450959992762022-11-10T09:53:00.001-06:002022-11-11T06:55:53.317-06:00Disable AutoPlay for PowerDVD<h1 id="abstract">Abstract</h1>
<p>I recently installed PowerDVD and it took over AutoPlay for pretty much all mounted media. This is really annoying. I wanted to stop PowerDVD from launching every time I put in a disc. Unfortunately, this wasn’t as easy as I thought it would be. This article describes how to stop PowerDVD AutoPlay.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="controlpanel">Control Panel</h1>
<p>PowerDVD does not have a setting to stop it from AutoPlay. Instead, what you need to do is go to the Window’s old-fashioned Control Panel. Once there, select “Hardware and Sound” then “AutoPlay”. Once on the AutoPlay configuration screen, look for all instances of “PowerDVD” and change the configuration to whatever option you want. Refer to figure 1.</p>
<h1 id="figure1-controlpanelautoplayconfiguration">Figure 1 - Control Panel AutoPlay Configuration</h1>
<figure>
<img src="https://dsm01pap005files.storage.live.com/y4mvMpNVb9icXJu-6qnwWgGwYXKVA50XKTqf9FXU1-N0hrCNH37a3367BrJx_LCfKFGEBMXuL9wuPhw1oLKQ3ocWGR_m0n5xStrSDOAqILRFWWyki9zkYskuoe6rTJjWkBMNGynNY6wpbfYs4Aw3fC5cYDycKTNZoqGXR1ba3-m1GvY4-hADJ7gBEZCCcY_5Vkn?width=1176&height=816&cropmode=none" alt="Control Panel AutoPlay Configuration" title="Control Panel AutoPlay Configuration" />
<figcaption>Control Panel AutoPlay Configuration</figcaption>
</figure>
<h1 id="summary">Summary</h1>
<p>That’s it, very simple. Enjoy!</p>mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-46110103973454984092020-04-24T10:18:00.001-05:002020-04-24T10:36:56.191-05:00Apache Derby Database JVM Security Policy<h1 id="abstract">Abstract</h1>
<p>I have already posted a number of blogs about Derby:</p>
<ul>
<li><a href="http://mjremijan.blogspot.com/2018/11/derby-database-backup.html" title="Derby Database Backup">Derby Database Backup</a></li>
<li><a href="https://mjremijan.blogspot.com/2018/08/multiple-derby-network-servers-on-same.html" title="Multiple Derby Network Servers on the same Host">Multiple Derby Network Servers on the same Host</a></li>
<li><a href="https://mjremijan.blogspot.com/2018/05/apache-derby-database-users-and.html" title="Apache Derby Database Users and Permissions">Apache Derby Database Users and Permissions</a></li>
<li><a href="https://mjremijan.blogspot.com/2014/03/integration-testing-with-maven-and-in.html" title="Integration Testing with Maven and an In-Memory Derby Database">Integration Testing with Maven and an In-Memory Derby Database</a></li>
</ul>
<p>This wasn't intended to be a series. But over the years I've been using Derby more and more. I started using Derby as my database of choice for my Microservice architecture. These are personal-use applications, so Derby is more than sufficient. Even though these are personal-use applications, I require <a href="https://mjremijan.blogspot.com/2018/08/multiple-derby-network-servers-on-same.html" title="Multiple Derby Network Servers on the same Host">multiple servers</a> with <a href="https://mjremijan.blogspot.com/2018/05/apache-derby-database-users-and.html" title="Apache Derby Database Users and Permissions">limited user permissions</a>, and of course <a href="http://mjremijan.blogspot.com/2018/11/derby-database-backup.html" title="Derby Database Backup">database backup and restoration</a>. The final requirement is security. I run my Derby databases on an Ubuntu Linux VM with the <code>derby</code> usr account. Although the <code>derby</code> usr account has limited permissions on the VM, any extra layer of security is good. So the purpose of this blog is to demonstrate how to run Derby with a <strong>Java security policy</strong> to limit the JVM's permissions and enhance runtime security.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>Apache Derby 10.14.2.0</li>
<li>Java zulu11.39.15-ca-jdk11.0.7-linux_x64</li>
</ul>
<p>I am not going to go through the process of downloading and installing these technologies. I'll leave that as an exercise for you.</p>
<blockquote>
<p><strong>NOTE</strong>
Starting with version 10.15, the Derby project has been updated to use the Java 9 module system. As a result, the JAR files have changed quite a bit. It's unlikely the security.policy below will work with version 10.15+. As of this blog's publication date, I've yet to try it.</p>
</blockquote>
<h1 id="linux-bash-scripts">Linux bash scripts</h1>
<p>In order to manage Derby to run with a Java security policy, you need 3 scripts. The 1st script will setup the setup environment variables to configure Derby. The 2nd script will start the Derby network server, passing the correct command line parameters. The 3rd will stop the Derby network server.</p>
<p>Listing 1.1 shows you the first of these scripts. It exports a number of system environment variables with configuration values specific to run Derby in your environment.</p>
<h1 id="listing-1-1-setenv-sh">Listing 1.1 - setenv.sh</h1>
<pre><code class="bash"><span class="hljs-meta">#!/bin/bash
</span>
<span class="hljs-built_in">export</span> DERBY_HOME=/home/derby/opt/derby
<span class="hljs-built_in">export</span> PATH=<span class="hljs-string">"<span class="hljs-variable">$DERBY_HOME</span>/bin:<span class="hljs-variable">$PATH</span>"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"DERBY_HOME=<span class="hljs-variable">$DERBY_HOME</span>"</span>
<span class="hljs-built_in">export</span> JAVA_HOME=/home/derby/opt/java
<span class="hljs-built_in">echo</span> <span class="hljs-string">"JAVA_HOME=<span class="hljs-variable">$JAVA_HOME</span>"</span>
<span class="hljs-built_in">export</span> NS_HOME=/var/<span class="hljs-built_in">local</span>/derby/1527
mkdir -p <span class="hljs-variable">$NS_HOME</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"NS_HOME=<span class="hljs-variable">$NS_HOME</span>"</span>
<span class="hljs-built_in">export</span> NS_PORT=1527
<span class="hljs-built_in">echo</span> <span class="hljs-string">"NS_PORT=<span class="hljs-variable">$NS_PORT</span>"</span>
<span class="hljs-built_in">export</span> NS_HOST=0.0.0.0
<span class="hljs-built_in">echo</span> <span class="hljs-string">"NS_HOST=<span class="hljs-variable">$NS_HOST</span>"</span>
<span class="hljs-built_in">export</span> DERBY_OPTS=<span class="hljs-string">""</span>
<span class="hljs-built_in">export</span> DERBY_OPTS=<span class="hljs-string">"<span class="hljs-variable">$DERBY_OPTS</span> -Dderby.drda.host=<span class="hljs-variable">$NS_HOST</span>"</span>
<span class="hljs-built_in">export</span> DERBY_OPTS=<span class="hljs-string">"<span class="hljs-variable">$DERBY_OPTS</span> -Dderby.drda.portNumber=<span class="hljs-variable">$NS_PORT</span>"</span>
<span class="hljs-built_in">export</span> DERBY_OPTS=<span class="hljs-string">"<span class="hljs-variable">$DERBY_OPTS</span> -Dderby.system.home=<span class="hljs-variable">$NS_HOME</span>"</span>
<span class="hljs-comment"># Security Policy</span>
<span class="hljs-built_in">export</span> DERBY_OPTS=<span class="hljs-string">"<span class="hljs-variable">$DERBY_OPTS</span> -Dderby.stream.error.logSeverityLevel=0"</span>
<span class="hljs-built_in">export</span> DERBY_OPTS=<span class="hljs-string">"<span class="hljs-variable">$DERBY_OPTS</span> -Dderby.security.port=<span class="hljs-variable">$NS_PORT</span>"</span>
<span class="hljs-built_in">export</span> DERBY_OPTS=<span class="hljs-string">"<span class="hljs-variable">$DERBY_OPTS</span> -Dderby.install.url=file:<span class="hljs-variable">$DERBY_HOME</span>/lib/"</span>
<span class="hljs-built_in">export</span> DERBY_OPTS=<span class="hljs-string">"<span class="hljs-variable">$DERBY_OPTS</span> -Djava.security.manager"</span>
<span class="hljs-built_in">export</span> DERBY_OPTS=<span class="hljs-string">"<span class="hljs-variable">$DERBY_OPTS</span> -Djava.security.policy=<span class="hljs-variable">$NS_HOME</span>/security.policy"</span>
</code></pre>
<p><strong>DERBY_HOME</strong> is self explanatory. It's where Derby is unzipped (installed). Add Derby's <code>bin</code> directory to the <code>PATH</code>.</p>
<p><strong>JAVA_HOME</strong> is self explanatory. It's where Java is unzipped (installed). Add Java's <code>bin</code> directory to the <code>PATH</code>.</p>
<p><strong>NS_HOME</strong> is "<strong>N</strong>etwork <strong>S</strong>erver Home". This is the directory the Derby network server will use to store its configuration and databases. Whenever a new database is created on this Derby network server, a new sub-directory will be created under <code>NS_HOME</code> for the new database. This allows multiple Derby network servers running on the same host to keep their data separate.</p>
<p><strong>NS_PORT</strong> is "<strong>N</strong>etwork <strong>S</strong>erver Port". It's the port the Derby network server uses to listen for connections. This allows multiple Derby network servers to run on the same host.</p>
<p><strong>NS_HOST</strong> is "<strong>N</strong>etwork <strong>S</strong>erver Host". It sets the network interface used by the Derby network server when listening for connections. By default, the Derby network server only listens for connections on the loopback address of <code>127.0.0.1</code>. This default means clients must run on the same host as the network server - not very useful. By setting the host to <code>0.0.0.0</code>, the Derby network server will listen for connections on any network interface on the host. If your VM has multiple network interfaces, <code>NS_HOST</code> should be set to the IP of one of those interfaces. Setting this value allows clients to be remote. </p>
<p><strong>DERBY_OPTS</strong> is the system property used to get all of the configuration options to Derby. Its value is created by concatenating together the appropriate Derby system properties with their associated values. The first 3 properties are needed to start Derby with or without a security policy.</p>
<ol>
<li>derby.drda.host</li>
<li>derby.drda.portNumber</li>
<li>derby.system.home</li>
</ol>
<p>The final 5 properties are needed for configuring Derby to run with a security policy.</p>
<ol>
<li>derby.stream.error.logSeverityLevel</li>
<li>derby.security.port</li>
<li>derby.install.url</li>
<li>java.security.manager</li>
<li>java.security.policy</li>
</ol>
<p>One of the most important properties is <code>java.security.policy=$NS_HOME/security.policy"</code>. The value of this property points to a <code>security.policy</code> file which will configure the Java <a href="https://docs.oracle.com/javase/7/docs/api/java/lang/SecurityManager.html" title="Java SecurityManager class"><code>SecurityManager</code></a>. You will read about creating the <code>security.policy</code> file in just a little bit. Next, you will look at the script for starting the server.</p>
<p>Listing 1.2 shows you the second of these scripts. It starts the Derby networks server, passing the correct command line parameters so Derby runs with a security policy.</p>
<h1 id="listing-1-2-start-sh">Listing 1.2 - start.sh</h1>
<pre><code class="bash"><span class="hljs-meta">#!/bin/bash
</span>
<span class="hljs-comment"># Directory of the script</span>
SD=$( <span class="hljs-built_in">cd</span> <span class="hljs-string">"<span class="hljs-variable">$( dirname "${BASH_SOURCE[0]}" )</span>"</span> && <span class="hljs-built_in">pwd</span> )
<span class="hljs-comment"># Source in common variables</span>
<span class="hljs-built_in">source</span> <span class="hljs-variable">$SD</span>/setenv.sh
<span class="hljs-comment"># Symlink the network server configurations</span>
ln -sf <span class="hljs-variable">$SD</span>/../conf/security.policy <span class="hljs-variable">$NS_HOME</span>/security.policy
ln -sf <span class="hljs-variable">$SD</span>/../conf/derby.properties <span class="hljs-variable">$NS_HOME</span>/derby.properties
startNetworkServer
</code></pre>
<p><strong>SD</strong> is <strong>S</strong>cript <strong>D</strong>irectory. The evaluation determines the fully-qualified file system location of the <code>start.sh</code> script and assigns it to <code>SD</code>. This is useful when referencing other scripts.</p>
<p><strong>source</strong> is self explanatory. It sources in the system environment variables to configure the Derby network server. See listing 1.1 for details.</p>
<p><strong>Symlink</strong> configuration is for the <code>security.policy</code> file and the <code>derby.properties</code> file. The purpose of the symlinks is to get these 2 files into the <code>$NS_HOME</code> directory. Derby looks for the <code>derby.properties</code> file in the <code>$NS_HOME</code> directory, so it needs to be there. For consistency (not a necessity), you want to put the <code>security.policy</code> file there as well. In listing 1.1 the <code>java.security.policy=$NS_HOME/security.policy"</code> property configures this location. For my environment, I have separated the <code>$NS_HOME</code> directory from the directory where I keep the management scripts and other Derby configuration files. The reason I do this is because of disaster recovery. I consider the <code>$NS_HOME</code> directory to be volitile, meaning if for some reason it goes missing (deleted, disk drive error, corrupted, new VM built, etc) I must be able to restore the database data, management scripts (<code>setenv.sh</code>, <code>start.sh</code>, <code>stop.sh</code>) and configuration files (<code>security.policy</code>, <code>derby.properties</code>) from my cloud backups. The <strong>real</strong> configuration files are kept outside of the <code>$NS_HOME</code> directory and <code>start.sh</code> symlinks them in the proper location.</p>
<p><strong>startNetworkServer</strong> is a script provided by Derby (<code>$DERBY_HOME/bin</code>) to start the network server. The <code>DERBY_OPTS</code> variable - set in <code>setenv.sh</code> - is used to configure the network server. By default, Derby runs with a limited security policy. However, since you configured the security policy, Derby will use your configuration instead of the default.</p>
<p>You now have the Derby server environment configuration and start script. What you don't have yet is the ability to stop the Derby network server. Stopping the server is easy. You will look at the script for stopping the server next.</p>
<blockquote>
<p><strong>NOTE</strong>
The <code>security.policy</code> file is also needed still. You will read about it in just a few moments, I promise!</p>
</blockquote>
<p>Listing 1.3 shows you the third of these scripts. It stops the Derby networks server. Not too exciting, but it's important to have a managed shutdown of the server to prevent data corruption.</p>
<h1 id="listing-1-3-stop-sh">Listing 1.3 - stop.sh</h1>
<pre><code class="bash"><span class="hljs-meta">#!/bin/bash
</span>
<span class="hljs-comment"># Directory of the script</span>
SD=$( <span class="hljs-built_in">cd</span> <span class="hljs-string">"<span class="hljs-variable">$( dirname "${BASH_SOURCE[0]}" )</span>"</span> && <span class="hljs-built_in">pwd</span> )
<span class="hljs-comment"># Source in common variables</span>
<span class="hljs-built_in">source</span> <span class="hljs-variable">$SD</span>/setenv.sh
stopNetworkServer
</code></pre>
<p>All of this is self explanatory. No further comments are needed for this script.</p>
<h1 id="the-security-policy-file">The security.policy file</h1>
<p>Derby comes with a demo security policy file. It is located in <code>DERBY_HOME/demo/templates/security.policy</code>. Using this file as the starting point, I was able to produce a final version that met my requirements for:</p>
<ul>
<li>Network (remote) access</li>
<li>Localhost access</li>
<li>Startup</li>
<li>Shutdown</li>
<li>Backup</li>
</ul>
<h1 id="listing-2-1-security-policy">Listing 2.1 - security.policy</h1>
<pre><code class="properties"><span class="hljs-comment">//</span>
<span class="hljs-comment">// Licensed to the Apache Software Foundation (ASF) under one or more</span>
<span class="hljs-comment">// contributor license agreements. See the NOTICE file distributed with</span>
<span class="hljs-comment">// this work for additional information regarding copyright ownership.</span>
<span class="hljs-comment">// The ASF licenses this file to You under the Apache License, Version 2.0</span>
<span class="hljs-comment">// (the "License"); you may not use this file except in compliance with</span>
<span class="hljs-comment">// the License. You may obtain a copy of the License at</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">// http://www.apache.org/licenses/LICENSE-2.0</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">// Unless required by applicable law or agreed to in writing, software</span>
<span class="hljs-comment">// distributed under the License is distributed on an "AS IS" BASIS,</span>
<span class="hljs-comment">// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span>
<span class="hljs-comment">// See the License for the specific language governing permissions and</span>
<span class="hljs-comment">// limitations under the License.</span>
<span class="hljs-comment">//</span>
grant codeBase <span class="hljs-string">"<span class="hljs-subst">${derby.install.url}</span>derby.jar"</span>
{
<span class="hljs-comment">// These permissions are needed for everyday, embedded Derby usage.</span>
<span class="hljs-comment">//</span>
permission java.lang.RuntimePermission <span class="hljs-string">"createClassLoader"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"derby.*"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"user.dir"</span>, <span class="hljs-string">"read"</span>;
permission org.apache.derby.security.SystemPermission <span class="hljs-string">"engine"</span>, <span class="hljs-string">"usederbyinternals"</span>;
<span class="hljs-comment">// The next two properties are used to determine if the VM is 32 or 64 bit.</span>
<span class="hljs-comment">//</span>
permission java.util.PropertyPermission <span class="hljs-string">"sun.arch.data.model"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"os.arch"</span>, <span class="hljs-string">"read"</span>;
permission java.io.FilePermission <span class="hljs-string">"<span class="hljs-subst">${derby.system.home}</span>"</span>,<span class="hljs-string">"read"</span>;
permission java.io.FilePermission <span class="hljs-string">"<span class="hljs-subst">${derby.system.home}</span><span class="hljs-subst">${/}</span>-"</span>,
<span class="hljs-string">"read,write,delete"</span>;
<span class="hljs-comment">// Needed by sysinfo. A file permission is needed to check the existence of</span>
<span class="hljs-comment">// jars on the classpath. You can limit this permission to just the locations</span>
<span class="hljs-comment">// which hold your jar files. This block is reproduced for all codebases</span>
<span class="hljs-comment">// which include the sysinfo classes--the policy file syntax does not let you</span>
<span class="hljs-comment">// grant permissions to several codebases all at once.</span>
<span class="hljs-comment">//</span>
permission java.util.PropertyPermission <span class="hljs-string">"user.*"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.home"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.class.path"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.runtime.version"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.fullversion"</span>, <span class="hljs-string">"read"</span>;
permission java.lang.RuntimePermission <span class="hljs-string">"getProtectionDomain"</span>;
permission java.io.FilePermission <span class="hljs-string">"java.runtime.version"</span>, <span class="hljs-string">"read"</span>;
permission java.io.FilePermission <span class="hljs-string">"java.fullversion"</span>, <span class="hljs-string">"read"</span>;
permission java.io.FilePermission <span class="hljs-string">"<span class="hljs-subst">${derby.install.path}</span><span class="hljs-subst">${/}</span>-"</span>, <span class="hljs-string">"read"</span>;
permission java.io.FilePermission <span class="hljs-string">"/tmp<span class="hljs-subst">${/}</span>-"</span>, <span class="hljs-string">"read,write,delete"</span>;
<span class="hljs-comment">// Permissions needed for JMX based management and monitoring.</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">// Allows this code to create an MBeanServer:</span>
<span class="hljs-comment">//</span>
permission javax.management.MBeanServerPermission <span class="hljs-string">"createMBeanServer"</span>;
<span class="hljs-comment">// Allows access to Derby's built-in MBeans, within the domain</span>
<span class="hljs-comment">// org.apache.derby. Derby must be allowed to register and unregister these</span>
<span class="hljs-comment">// MBeans. To fine tune this permission, see the javadoc of</span>
<span class="hljs-comment">// javax.management.MBeanPermission or the JMX Instrumentation and Agent</span>
<span class="hljs-comment">// Specification.</span>
<span class="hljs-comment">//</span>
permission javax.management.MBeanPermission
<span class="hljs-string">"org.apache.derby.*#[org.apache.derby:*]"</span>,
<span class="hljs-string">"registerMBean,unregisterMBean"</span>;
<span class="hljs-comment">// Trusts Derby code to be a source of MBeans and to register these in the</span>
<span class="hljs-comment">// MBean server.</span>
<span class="hljs-comment">//</span>
permission javax.management.MBeanTrustPermission <span class="hljs-string">"register"</span>;
<span class="hljs-comment">// Gives permission for jmx to be used against Derby but only if JMX</span>
<span class="hljs-comment">// authentication is not being used. In that case the application would need</span>
<span class="hljs-comment">// to create a whole set of fine-grained permissions to allow specific users</span>
<span class="hljs-comment">// access to MBeans and actions they perform.</span>
<span class="hljs-comment">//</span>
permission org.apache.derby.security.SystemPermission <span class="hljs-string">"jmx"</span>, <span class="hljs-string">"control"</span>;
permission org.apache.derby.security.SystemPermission <span class="hljs-string">"engine"</span>, <span class="hljs-string">"monitor"</span>;
permission org.apache.derby.security.SystemPermission <span class="hljs-string">"server"</span>, <span class="hljs-string">"monitor"</span>;
<span class="hljs-comment">// getProtectionDomain is an optional permission needed for printing</span>
<span class="hljs-comment">// classpath information to derby.log</span>
<span class="hljs-comment">//</span>
permission java.lang.RuntimePermission <span class="hljs-string">"getProtectionDomain"</span>;
<span class="hljs-comment">// The following permission must be granted for Connection.abort(Executor) to</span>
<span class="hljs-comment">// work. Note that this permission must also be granted to outer</span>
<span class="hljs-comment">// (application) code domains.</span>
<span class="hljs-comment">//</span>
permission java.sql.SQLPermission <span class="hljs-string">"callAbort"</span>;
permission java.sql.SQLPermission <span class="hljs-string">"deregisterDriver"</span>;
<span class="hljs-comment">// Needed by FileUtil#limitAccessToOwner</span>
<span class="hljs-comment">//</span>
permission java.lang.RuntimePermission <span class="hljs-string">"accessUserInformation"</span>;
permission java.lang.RuntimePermission <span class="hljs-string">"getFileStoreAttributes"</span>;
};
grant codeBase <span class="hljs-string">"<span class="hljs-subst">${derby.install.url}</span>derbynet.jar"</span>
{
<span class="hljs-comment">// These permissions lets the Network Server manage connections from clients.</span>
<span class="hljs-comment">// Accept connections from any host. Derby is listening to the host interface</span>
<span class="hljs-comment">// specified via the -h option to "NetworkServerControl start" on the command</span>
<span class="hljs-comment">// line, via the address parameter to the</span>
<span class="hljs-comment">// org.apache.derby.drda.NetworkServerControl constructor in the API or via</span>
<span class="hljs-comment">// the property derby.drda.host; the default is localhost. You may want to</span>
<span class="hljs-comment">// restrict allowed hosts, e.g. to hosts in a specific subdomain,</span>
<span class="hljs-comment">// e.g. "*.example.com".</span>
<span class="hljs-comment">//</span>
permission java.net.SocketPermission <span class="hljs-string">"*"</span>, <span class="hljs-string">"accept"</span>;
<span class="hljs-comment">// Allow the server to listen to the socket on the port specified with the</span>
<span class="hljs-comment">// -p option to "NetworkServerControl start" on the command line, or with</span>
<span class="hljs-comment">// the portNumber parameter to the NetworkServerControl constructor in the</span>
<span class="hljs-comment">// API, or with the property derby.drda.portNumber. The default is 1527. </span>
permission java.net.SocketPermission <span class="hljs-string">"localhost:<span class="hljs-subst">${derby.security.port}</span>"</span>,
<span class="hljs-string">"listen"</span>;
permission java.net.SocketPermission <span class="hljs-string">"<span class="hljs-subst">${derby.drda.host}</span>:<span class="hljs-subst">${derby.security.port}</span>"</span>,
<span class="hljs-string">"listen"</span>;
<span class="hljs-comment">// Needed for server tracing.</span>
<span class="hljs-comment">//</span>
permission java.io.FilePermission <span class="hljs-string">"<span class="hljs-subst">${derby.drda.traceDirectory}</span><span class="hljs-subst">${/}</span>-"</span>,
<span class="hljs-string">"read,write,delete"</span>;
<span class="hljs-comment">// Needed by FileUtil#limitAccessToOwner</span>
<span class="hljs-comment">//</span>
permission java.lang.RuntimePermission <span class="hljs-string">"accessUserInformation"</span>;
permission java.lang.RuntimePermission <span class="hljs-string">"getFileStoreAttributes"</span>;
<span class="hljs-comment">// Needed for NetworkServerMBean access (see JMX section above)</span>
<span class="hljs-comment">//</span>
permission org.apache.derby.security.SystemPermission <span class="hljs-string">"server"</span>,
<span class="hljs-string">"control,monitor"</span>;
permission org.apache.derby.security.SystemPermission <span class="hljs-string">"engine"</span>, <span class="hljs-string">"usederbyinternals"</span>;
<span class="hljs-comment">// Needed by sysinfo. A file permission is needed to check the existence of</span>
<span class="hljs-comment">// jars on the classpath. You can limit this permission to just the locations</span>
<span class="hljs-comment">// which hold your jar files. This block is reproduced for all codebases</span>
<span class="hljs-comment">// which include the sysinfo classes--the policy file syntax does not let you</span>
<span class="hljs-comment">// grant permissions to several codebases all at once.</span>
<span class="hljs-comment">//</span>
permission java.util.PropertyPermission <span class="hljs-string">"user.*"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.home"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.class.path"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.runtime.version"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.fullversion"</span>, <span class="hljs-string">"read"</span>;
permission java.lang.RuntimePermission <span class="hljs-string">"getProtectionDomain"</span>;
permission java.io.FilePermission <span class="hljs-string">"java.runtime.version"</span>, <span class="hljs-string">"read"</span>;
permission java.io.FilePermission <span class="hljs-string">"java.fullversion"</span>, <span class="hljs-string">"read"</span>;
permission java.io.FilePermission <span class="hljs-string">"<span class="hljs-subst">${derby.install.path}</span><span class="hljs-subst">${/}</span>-"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"derby.*"</span>, <span class="hljs-string">"read,write"</span>;
permission java.net.SocketPermission <span class="hljs-string">"localhost:<span class="hljs-subst">${derby.security.port}</span>"</span>, <span class="hljs-string">"connect,resolve"</span>;
permission java.net.SocketPermission <span class="hljs-string">"<span class="hljs-subst">${derby.drda.host}</span>:<span class="hljs-subst">${derby.security.port}</span>"</span>, <span class="hljs-string">"connect,resolve"</span>;
};
grant codeBase <span class="hljs-string">"<span class="hljs-subst">${derby.install.url}</span>derbytools.jar"</span>
{
<span class="hljs-comment">// Needed by sysinfo. A file permission is needed to check the existence of</span>
<span class="hljs-comment">// jars on the classpath. You can limit this permission to just the locations</span>
<span class="hljs-comment">// which hold your jar files. This block is for all codebases which include</span>
<span class="hljs-comment">// the sysinfo classes--the policy file syntax does not let you grant</span>
<span class="hljs-comment">// permissions to several codebases all at once.</span>
<span class="hljs-comment">//</span>
permission java.util.PropertyPermission <span class="hljs-string">"user.*"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.home"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.class.path"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.runtime.version"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.fullversion"</span>, <span class="hljs-string">"read"</span>;
permission java.lang.RuntimePermission <span class="hljs-string">"getProtectionDomain"</span>;
permission java.io.FilePermission <span class="hljs-string">"<<ALL FILES>>"</span>, <span class="hljs-string">"read"</span>;
permission java.io.FilePermission <span class="hljs-string">"java.runtime.version"</span>, <span class="hljs-string">"read"</span>;
permission java.io.FilePermission <span class="hljs-string">"java.fullversion"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"*"</span>, <span class="hljs-string">"read,write"</span>;
};
grant codeBase <span class="hljs-string">"<span class="hljs-subst">${derby.install.url}</span>derbyclient.jar"</span>
{
<span class="hljs-comment">// Needed by sysinfo. A file permission is needed to check the existence of</span>
<span class="hljs-comment">// jars on the classpath. You can limit this permission to just the locations</span>
<span class="hljs-comment">// which hold your jar files. This block is reproduced for all codebases</span>
<span class="hljs-comment">// which include the sysinfo classes--the policy file syntax does not let you</span>
<span class="hljs-comment">// grant permissions to several codebases all at once.</span>
<span class="hljs-comment">//</span>
permission java.util.PropertyPermission <span class="hljs-string">"user.*"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.home"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.class.path"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.runtime.version"</span>, <span class="hljs-string">"read"</span>;
permission java.util.PropertyPermission <span class="hljs-string">"java.fullversion"</span>, <span class="hljs-string">"read"</span>;
permission java.lang.RuntimePermission <span class="hljs-string">"getProtectionDomain"</span>;
permission java.io.FilePermission <span class="hljs-string">"<span class="hljs-subst">${derby.install.path}</span><span class="hljs-subst">${/}</span>-"</span>, <span class="hljs-string">"read"</span>;
<span class="hljs-comment">// The following permission must be granted for Connection.abort(Executor) to</span>
<span class="hljs-comment">// work. Note that this permission must also be granted to outer</span>
<span class="hljs-comment">// (application) code domains.</span>
<span class="hljs-comment">//</span>
permission java.sql.SQLPermission <span class="hljs-string">"callAbort"</span>;
permission java.net.SocketPermission <span class="hljs-string">"localhost:<span class="hljs-subst">${derby.security.port}</span>"</span>, <span class="hljs-string">"connect,resolve"</span>;
permission java.net.SocketPermission <span class="hljs-string">"<span class="hljs-subst">${derby.drda.host}</span>:<span class="hljs-subst">${derby.security.port}</span>"</span>, <span class="hljs-string">"connect,resolve"</span>;
};
</code></pre>
<p>Policy files are a lot to take in. After 20 years using Java, I've only come across them just a handful of times. I don't pretend to know everything that goes into a policy file. All I know is this file is working for all my requirements. Each Derby update requires testing and maybe some tweeking. The derby-users@db.apache.org mailing list is your best source of information.</p>
<p>A big shout out to Rick Hillegas from the derby-users@db.apache.org mailing list for helping me get to this version of the policy file. He provided most of it and I added the following to meet my requirements.</p>
<p>Line 50 <strong><code>permission java.io.FilePermission "/tmp${/}-", "read,write,delete";</code></strong>. My <a href="http://mjremijan.blogspot.com/2018/11/derby-database-backup.html" title="Derby Database Backup">database backup process</a> uses <code>CALL SYSCS_UTIL.SYSCS_BACKUP_DATABASE (‘/tmp/resiste-backup/1527’)</code>. So the <code>derby.jar</code> file needs read,write,delete permissions to the <code>/tmp</code> directory on the file system so it can write the backup into that directory. </p>
<p>Line 92 <strong><code>permission java.sql.SQLPermission "deregisterDriver";</code></strong>. When administering my Derby database with the the <code>ij</code> tool, found an exception in the <code>derby.log</code> file about <code>deregisterDriver</code>. So I added this permission to the <code>derby.jar</code> file as well.</p>
<p>Line 160 <strong><code>permission java.net.SocketPermission "${derby.drda.host}:${derby.security.port}", "connect,resolve";</code></strong>. Properties <code>derby.drda.host</code> and <code>derby.security.port</code> are set in the <code>setenv.sh</code> script (listing 1.1). I had to add this permission because my Derby network server is accessed by remote (non-localhost) clients. In <code>setenv.sh</code>, I use <code>-Dderby.drda.host=0.0.0.0</code> to override the default localhost-only interface listening. I also found I needed this in the policy file while testing the <code>stop.sh</code> script (listing 1.3).</p>
<h1 id="summary">Summary</h1>
<p>That's it. I hope you enjoyed learning how to run a Derby network server with a security policy.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-12724710772557923962020-04-22T13:57:00.000-05:002020-04-23T20:04:53.488-05:00Encrypt with OpenSSL, Decrypt with Java, Using OpenSSL RSA Public Private Keys<h1 id="abstract">Abstract</h1>
<p>In 2017 I wrote a <a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_5.html" title="3 part series on encryption algorithms">3 part series</a> on choosing the best hashing and encryption algorithms. While doing the research for the series, I learned a lot about hashing and encryption. The most important thing I learned is that although I must educate myself on how to use the safest algorithms possible, I must also leave the development of these algorithms to the experts. With that said, I started thinking about Java's interoperability with encryption experts, specifically OpenSSL. My <a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_5.html" title="3 part series on encryption algorithms">3 part series</a> focused only on encryption from the Java point of view. I wondered how difficult it would be for Java to interoperate with a tool like OpenSSL. The purpose of this blog is to demonstrate Java's interoperability with OpenSSL:</p>
<ul>
<li>Generate private and public keys with OpenSSL</li>
<li>Encrypt values with OpenSSL</li>
<li>Decrypt values with Java</li>
</ul>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>OpenJDK Runtime Environment Zulu11.39+15-CA (build 11.0.7+10-LTS)</li>
<li>OpenSSL 1.1.1c 28 May 2019</li>
<li>Apache NetBeans IDE 11.3</li>
<li>Maven 3.3.9 (Bundled with NetBeans)</li>
</ul>
<pre><code class="xml"><span class="hljs-tag"><<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.junit.jupiter<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>junit-jupiter-api<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>5.5.2<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">scope</span>></span>test<span class="hljs-tag"></<span class="hljs-name">scope</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.junit.jupiter<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>junit-jupiter-params<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>5.5.2<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">scope</span>></span>test<span class="hljs-tag"></<span class="hljs-name">scope</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.junit.jupiter<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>junit-jupiter-engine<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>5.5.2<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">scope</span>></span>test<span class="hljs-tag"></<span class="hljs-name">scope</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependencies</span>></span>
</code></pre>
<pre><code class="xml"><span class="hljs-tag"><<span class="hljs-name">pluginManagement</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-clean-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>2.5<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-resources-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>2.6<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-compiler-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>3.8.1<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"><<span class="hljs-name">debug</span>></span>true<span class="hljs-tag"></<span class="hljs-name">debug</span>></span>
<span class="hljs-tag"></<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-surefire-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>3.0.0-M4<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"><<span class="hljs-name">argLine</span>></span>-Dfile.encoding=UTF8<span class="hljs-tag"></<span class="hljs-name">argLine</span>></span>
<span class="hljs-tag"></<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-jar-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>2.4<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-install-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>2.4<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"></<span class="hljs-name">pluginManagement</span>></span>
</code></pre>
<h1 id="download">Download</h1>
<p>Visit my GitHub page <a href="https://github.com/mjremijan">https://github.com/mjremijan</a> to see all of my open source projects. The code for this post is located in: <a href="https://github.com/mjremijan/thoth-rsa" title="thoth-rsa">https://github.com/mjremijan/thoth-rsa</a></p>
<h1 id="background">Background</h1>
<p>I started wondering about being able to interoperate OpenSSL and Java as I was modularizing Monolith applications with Microservices. When using Microservices, applications still need to encrypt and decrypt sensitive configuration data - such as database passwords - yet the small runtimes used by Microservices make this a challenge.</p>
<p>With a Monolith architecture, the Java/Jakarta EE application server handles encryption and decryption for an application. Managed resources such as database connection pools are configured within the EE application server and other other encrypted values may be generally stored within JNDI. In both cases, the server provides both encryption and decryption without the application knowing any of the details. The application is either provided a managed resource or a decrypted value by the application server.</p>
<p>However, in a Microservice architecture, runtimes (such as Spring Boot) are kept "small" and do not provide as many features as an EE application server. A database connection is a good example. It's easy to configure a database connection in Spring Boot, however how do you support password encryption and decryption? It now must be supported by DevOps and the Development team.</p>
<blockquote>
<p><strong>NOTE</strong>
Other Microservice technologies like Kubernetes are working to fill the gap and provide encryption features similar to EE application servers.</p>
</blockquote>
<p>So this got me thinking. DevOps lives in the Linux/Unix world. Developers live in the Java world. Why not bring the 2 worlds together to support an encryption/decryption strategy? This would allow DevOps and Developers to do what they each do best. In order to do this, I first needed clearly defined goals.</p>
<h1 id="goals">Goals</h1>
<p>Migration from a Monolith architecture to Microservices is slow. Yes, there are Microservice infrastructure solutions for encryption and decryption. However, those won't help you in the 3-5 year transition period when that infrastructure isn't available. To support the transition, I decided on the following goals.</p>
<ol>
<li>Encryption tool of choice is OpenSSL. It is on every Linux/Unix system, is an industry standard, and will be familiar to all DevOps teams.</li>
<li>Encryption performed by DevOps, or another team, so there is a separation of responsibilities. No one on the development team may know an unencrypted value.</li>
<li>All environments will use their own keys. No key sharing.</li>
<li>All keys and encrypted values may be regenerated at any time with no change to the application.</li>
<li>Encryption will be either of an entire file or of specific values within a (properties) file.</li>
<li>Encrypted values and keys are made available to the Java runtime using a strategy agreed upon and enforced by both DevOps and Development teams.</li>
<li>Decryption is performed by the Java application for whatever purposes it needs. Don't log the encrypted values!</li>
</ol>
<p>With these goals in mind, let's take a journey to get there.</p>
<h1 id="which-algorithm-to-use">Which Algorithm to Use</h1>
<p>The first question I needed to answer is which encryption algorithm to use. For encryption I have a choice between single key symmetric encryption or public/private key asymmetry encryption. My choice is:</p>
<p><strong>RSA-4096 public/private key asymmetric encryption</strong></p>
<p>The reason for choosing an asymmetric encryption algorithm is because the public/private keys allow for the highest possible level of separation of responsibilities. There may be separate teams for generating the keys, encrypting the values, and putting everything together for runtime. In reality this may all be done by one team or even a single person, but an asymmetric encryption algorithm gives flexibility separating these concerns.</p>
<p>As for using the RSA-4096 algorithm, according to my research, it's the best and most secure today (Remijan, 2017).</p>
<p>Now we know which algorithm to use. Next, we'll look at generating the private key.</p>
<h1 id="openssl-generate-the-private-key">OpenSSL Generate the Private Key</h1>
<p>In Java, the <code>PKCS8EncodedKeySpec</code> class expects the RSA private key with a <code>PKCS8</code> encoding. (Java Code, n.d.). I found 2 ways of doing this with OpenSSL.</p>
<h1 id="listing-2-1-generate-private-key-with-2-commands">Listing 2.1 - Generate Private Key with 2 Commands</h1>
<pre><code class="bash"># Generate <span class="hljs-keyword">private</span> key <span class="hljs-keyword">with</span> pkcs1 encoding
openssl genrsa -<span class="hljs-keyword">out</span> private_key_rsa_4096_pkcs1.pem <span class="hljs-number">4096</span>
# Convert <span class="hljs-keyword">private</span> key <span class="hljs-keyword">to</span> pkcs8 encoding
openssl pkcs8 -topk8 -<span class="hljs-keyword">in</span> private_key_rsa_4096_pkcs1.pem -inform pem -<span class="hljs-keyword">out</span> private_key_rsa_4096_pkcs8-exported.pem -outform pem -nocrypt
</code></pre>
<p>In listing 2.1 (destan, 2017), the private key is generated with 2 commands. The first command generates the key with a <code>PKCS1</code> encoding. The second command converts the <code>PKCS1</code> encoded key to a key with <code>PKCS8</code> encoding.</p>
<h1 id="listing-2-2-generate-private-key-with-1-command">Listing 2.2 - Generate Private Key with 1 Command</h1>
<pre><code class="bash"># Generate <span class="hljs-keyword">private</span> key <span class="hljs-keyword">with</span> pkcs8 encoding
openssl genpkey -<span class="hljs-keyword">out</span> private_key_rsa_4096_pkcs8-generated.pem -algorithm RSA -pkeyopt rsa_keygen_bits:<span class="hljs-number">4096</span>
</code></pre>
<p>In listing 2.2, the private key is generated using a single command. This produces a key with a <code>PKCS8</code> encoding. No additional conversion is needed.</p>
<p>Whether you use listing 2.1 or 2.2 to generate the private key, when generated it will look something like this.</p>
<pre><code class="txt">-<span class="ruby">----<span class="hljs-keyword">BEGIN</span> PRIVATE KEY-----
</span>MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDVgLrCSDC5mLRL
JY+okYX5MOMGi+bvtRQ9qIQ90d3BO1gAao6ZsbPEFxnOTR9Q3bGsEE5oRlh/FSYS
.
.
kvCjd0ineNZ6OgPVJ/mhPULsZb11+noSUPmFqvClb8SQ0BipbKIcSTIJlQt1ZRZ2
INdXsP5kNlRK181jtU/xtQYfwSjkKA==
-<span class="ruby">----<span class="hljs-keyword">END</span> PRIVATE KEY-----</span>
</code></pre>
<p>Great! The private key is generated! Now let's move on to generating the public key.</p>
<h1 id="openssl-generate-the-public-key">OpenSSL Generate the Public Key</h1>
<p>In Java, the <code>X509EncodedKeySpec</code> class expects the RSA public key with an <code>X509</code> encoding. (Java Code, n.d.). The public key is generated from the private key, so you must have the private key first.</p>
<h1 id="listing-3-1-generate-public-key">Listing 3.1 - Generate Public Key</h1>
<pre><code class="bash"># <span class="hljs-keyword">Export</span> <span class="hljs-keyword">public</span> key <span class="hljs-keyword">in</span> pkcs8 format
openssl rsa -pubout -outform pem -<span class="hljs-keyword">in</span> private_key_rsa_4096_pkcs8-generated.pem -<span class="hljs-keyword">out</span> public_key_rsa_4096_pkcs8-exported.pem
</code></pre>
<p>Listing 3.1 shows the command using the private key <code>private_key_rsa_4096_pkcs8-generated.pem</code> to generate the public key <code>public_key_rsa_4096_pkcs8-exported.pem</code>.</p>
<p>The public key will look something like this.</p>
<pre><code class="txt">-<span class="ruby">----<span class="hljs-keyword">BEGIN</span> PUBLIC KEY-----
</span>MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1YC6wkgwuZi0SyWPqJGF
+TDjBovm77UUPaiEPdHdwTtYAGqOmbGzxBcZzk0fUN2xrBBOaEZYfxUmEkOFzPbF
.
.
oNta8CSsVrqgFW/tI6+MQwrQFEOcBPCbh6Pr7NbiuR2LrfoJhUJlD5ofz5eM0419
JSS0RvKh0dF3ddlOKV/TQUsCAwEAAQ==
-<span class="ruby">----<span class="hljs-keyword">END</span> PUBLIC KEY-----</span>
</code></pre>
<p>Great! We have both the private key and the public key and both were generated by OpenSSL. Next, we need Java to use these key files. Do do that, we'll need to create instances of the <code>KeyFactory</code>, <code>PrivateKey</code>, and <code>PublicKey</code> objects. Let's dive into some Java code!</p>
<h1 id="java-keyfactory-privatekey-publickey">Java KeyFactory, PrivateKey, PublicKey</h1>
<p>After using OpenSSL to generate private and public key files, it is time for some Java code. Listing 4.1 is my complete <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> class. I discuss each individual method in detail below.</p>
<h1 id="listing-4-1-rsa4096-class">Listing 4.1 - Rsa4096 class</h1>
<pre><code class="java"><span class="hljs-keyword">package</span> org.thoth.rsa;
<span class="hljs-keyword">import</span> java.io.InputStream;
<span class="hljs-keyword">import</span> java.security.KeyFactory;
<span class="hljs-keyword">import</span> java.security.PrivateKey;
<span class="hljs-keyword">import</span> java.security.PublicKey;
<span class="hljs-keyword">import</span> java.security.spec.KeySpec;
<span class="hljs-keyword">import</span> java.security.spec.PKCS8EncodedKeySpec;
<span class="hljs-keyword">import</span> java.security.spec.X509EncodedKeySpec;
<span class="hljs-keyword">import</span> java.util.Base64;
<span class="hljs-keyword">import</span> javax.crypto.Cipher;
<span class="hljs-comment">/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/</span>
<span class="hljs-keyword">public</span> class Rsa4096 {
<span class="hljs-keyword">private</span> KeyFactory keyFactory;
<span class="hljs-keyword">private</span> PrivateKey privateKey;
<span class="hljs-keyword">private</span> PublicKey publicKey;
<span class="hljs-keyword">public</span> Rsa4096(
<span class="hljs-keyword">String</span> privateKeyClassPathResource
, <span class="hljs-keyword">String</span> publicKeyClassPathResource
) <span class="hljs-keyword">throws</span> Exception {
setKeyFactory();
setPrivateKey(privateKeyClassPathResource);
setPublicKey(publicKeyClassPathResource);
}
<span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> setKeyFactory() <span class="hljs-keyword">throws</span> Exception {
<span class="hljs-keyword">this</span>.keyFactory = KeyFactory.getInstance(<span class="hljs-string">"RSA"</span>);
}
<span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> setPrivateKey(<span class="hljs-keyword">String</span> classpathResource)
<span class="hljs-keyword">throws</span> Exception {
InputStream is = <span class="hljs-keyword">this</span>
.getClass()
.getClassLoader()
.getResourceAsStream(classpathResource);
<span class="hljs-keyword">String</span> stringBefore
= <span class="hljs-keyword">new</span> <span class="hljs-keyword">String</span>(is.readAllBytes());
is.close();
<span class="hljs-keyword">String</span> stringAfter = stringBefore
.replaceAll(<span class="hljs-string">"\\n"</span>, <span class="hljs-string">""</span>)
.replaceAll(<span class="hljs-string">"-----BEGIN PRIVATE KEY-----"</span>, <span class="hljs-string">""</span>)
.replaceAll(<span class="hljs-string">"-----END PRIVATE KEY-----"</span>, <span class="hljs-string">""</span>)
.<span class="hljs-built_in">trim</span>();
<span class="hljs-built_in">byte</span>[] decoded = Base64
.getDecoder()
.decode(stringAfter);
KeySpec keySpec
= <span class="hljs-keyword">new</span> PKCS8EncodedKeySpec(decoded);
privateKey = keyFactory.generatePrivate(keySpec);
}
<span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> setPublicKey(<span class="hljs-keyword">String</span> classpathResource)
<span class="hljs-keyword">throws</span> Exception {
InputStream is = <span class="hljs-keyword">this</span>
.getClass()
.getClassLoader()
.getResourceAsStream(classpathResource);
<span class="hljs-keyword">String</span> stringBefore
= <span class="hljs-keyword">new</span> <span class="hljs-keyword">String</span>(is.readAllBytes());
is.close();
<span class="hljs-keyword">String</span> stringAfter = stringBefore
.replaceAll(<span class="hljs-string">"\\n"</span>, <span class="hljs-string">""</span>)
.replaceAll(<span class="hljs-string">"-----BEGIN PUBLIC KEY-----"</span>, <span class="hljs-string">""</span>)
.replaceAll(<span class="hljs-string">"-----END PUBLIC KEY-----"</span>, <span class="hljs-string">""</span>)
.<span class="hljs-built_in">trim</span>()
;
<span class="hljs-built_in">byte</span>[] decoded = Base64
.getDecoder()
.decode(stringAfter);
KeySpec keySpec
= <span class="hljs-keyword">new</span> X509EncodedKeySpec(decoded);
publicKey = keyFactory.generatePublic(keySpec);
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">String</span> encryptToBase64(<span class="hljs-keyword">String</span> plainText) {
<span class="hljs-keyword">String</span> encoded = <span class="hljs-keyword">null</span>;
<span class="hljs-keyword">try</span> {
Cipher cipher = Cipher.getInstance(<span class="hljs-string">"RSA"</span>);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
<span class="hljs-built_in">byte</span>[] encrypted = cipher.doFinal(plainText.getBytes());
encoded = Base64.getEncoder().encodeToString(encrypted);
} <span class="hljs-keyword">catch</span> (Exception e) {
e.printStackTrace();
}
<span class="hljs-keyword">return</span> encoded;
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">String</span> decryptFromBase64(<span class="hljs-keyword">String</span> base64EncodedEncryptedBytes) {
<span class="hljs-keyword">String</span> plainText = <span class="hljs-keyword">null</span>;
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">final</span> Cipher cipher = Cipher.getInstance(<span class="hljs-string">"RSA"</span>);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
<span class="hljs-built_in">byte</span>[] decoded = Base64
.getDecoder()
.decode(base64EncodedEncryptedBytes);
<span class="hljs-built_in">byte</span>[] decrypted = cipher.doFinal(decoded);
plainText = <span class="hljs-keyword">new</span> <span class="hljs-keyword">String</span>(decrypted);
} <span class="hljs-keyword">catch</span> (Exception ex) {
ex.printStackTrace();
}
<span class="hljs-keyword">return</span> plainText;
}
}
</code></pre>
<h2 id="constructor">Constructor</h2>
<pre><code class="java"> <span class="hljs-selector-tag">public</span> <span class="hljs-selector-tag">Rsa4096</span>(
String privateKeyClassPathResource
, String publicKeyClassPathResource
) <span class="hljs-selector-tag">throws</span> <span class="hljs-selector-tag">Exception</span> {
<span class="hljs-selector-tag">setKeyFactory</span>();
<span class="hljs-selector-tag">setPrivateKey</span>(privateKeyClassPathResource);
<span class="hljs-selector-tag">setPublicKey</span>(publicKeyClassPathResource);
}
</code></pre>
<p>The constructor is simple and takes 2 parameters. By the names of the parameters you can guess what they are. The 1st parameter is the fully-qualified Class Path location of the private key file generated by OpenSSL. The 2nd parameter is the same for the public key file.</p>
<p><strong>Why put the key files on the Class Path?</strong> I'm using Maven to run unit tests to research this code. Maven makes is easy to make resources available on the Class Path, so that's what I'm using here. Again, this is research (See Disclaimer)!</p>
<p>Remember, one of the goals is to make the keys available to the Java runtime using a strategy agreed upon and enforced by both DevOps and Development teams. So your strategy may be different, but the end goal is the same: point to some location where you can read the bytes of the files.</p>
<h2 id="setkeyfactory-">setKeyFactory()</h2>
<pre><code class="java"> <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setKeyFactory</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-keyword">this</span>.keyFactory = KeyFactory.getInstance(<span class="hljs-string">"RSA"</span>);
}
</code></pre>
<p>The <code>setKeyFactory()</code> method instantiates a <code>KeyFactory</code> class for the <code>RSA</code> algorithm. Really simple; one line of code. You'll use this object later to build the <code>PrivateKey</code> and the <code>PublicKey</code>...it is a <strong>factory</strong> class after all :)</p>
<h2 id="setprivatekey-">setPrivateKey()</h2>
<pre><code class="java"> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> setPrivateKey(<span class="hljs-keyword">String</span> classpathResource)
<span class="hljs-keyword">throws</span> Exception {
InputStream is = <span class="hljs-keyword">this</span>
.getClass()
.getClassLoader()
.getResourceAsStream(classpathResource);
<span class="hljs-keyword">String</span> stringBefore
= <span class="hljs-keyword">new</span> <span class="hljs-keyword">String</span>(is.readAllBytes());
<span class="hljs-keyword">String</span> stringAfter = stringBefore
.replaceAll(<span class="hljs-string">"\\n"</span>, <span class="hljs-string">""</span>)
.replaceAll(<span class="hljs-string">"-----BEGIN PRIVATE KEY-----"</span>, <span class="hljs-string">""</span>)
.replaceAll(<span class="hljs-string">"-----END PRIVATE KEY-----"</span>, <span class="hljs-string">""</span>)
.<span class="hljs-built_in">trim</span>();
<span class="hljs-built_in">byte</span>[] decoded = Base64
.getDecoder()
.decode(stringAfter);
KeySpec keySpec
= <span class="hljs-keyword">new</span> PKCS8EncodedKeySpec(decoded);
privateKey = keyFactory.generatePrivate(keySpec);
}
</code></pre>
<p>The <code>setPrivateKey()</code> method instantiates a <code>PrivateKey</code>. In this method, the <code>ClassLoader</code> is used to get an <code>InputStream</code> to the private key file on the Class Path. The bytes of the file are read into a new <code>String</code>. Next, the <code>String</code> is processed as follows:</p>
<pre><code class="java"> String stringAfter = stringBefore
<span class="hljs-meta"> .replaceAll</span>(<span class="hljs-string">"\\n"</span>, <span class="hljs-string">""</span>)
<span class="hljs-meta"> .replaceAll</span>(<span class="hljs-string">"-----BEGIN PRIVATE KEY-----"</span>, <span class="hljs-string">""</span>)
<span class="hljs-meta"> .replaceAll</span>(<span class="hljs-string">"-----END PRIVATE KEY-----"</span>, <span class="hljs-string">""</span>)
<span class="hljs-meta"> .trim</span>()<span class="hljs-comment">;</span>
</code></pre>
<p>This processing is necessary because even though we used OpenSSL to generate a private key file with <code>PKCS8</code> encoding, the file is not directly usable by Java. If you try without the above processing, you'll get the following exception:</p>
<pre><code class="txt">java<span class="hljs-selector-class">.security</span><span class="hljs-selector-class">.spec</span><span class="hljs-selector-class">.InvalidKeySpecException</span>: java<span class="hljs-selector-class">.security</span><span class="hljs-selector-class">.InvalidKeyException</span>: invalid key format
</code></pre>
<p>The <code>PKCS8EncodedKeySpec</code> class expects the private key to be a single line of text with all comments removed (Java Code Example..., n.d.). This is the reason for the processing.</p>
<p>After processing removes the newlines and comments, the <code>PKCS8EncodedKeySpec</code> and <code>KeyFactory</code> are used to create the <code>PrivateKey</code>.</p>
<pre><code class="java"><span class="hljs-attribute"> KeySpec keySpec</span>
= new PKCS8EncodedKeySpec(decoded);
<span class="hljs-attribute"> privateKey</span> = keyFactory.generatePrivate(keySpec);
</code></pre>
<h2 id="setpublickey-">setPublicKey()</h2>
<pre><code class="java"> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> setPublicKey(<span class="hljs-keyword">String</span> classpathResource)
<span class="hljs-keyword">throws</span> Exception {
InputStream is = <span class="hljs-keyword">this</span>
.getClass()
.getClassLoader()
.getResourceAsStream(classpathResource);
<span class="hljs-keyword">String</span> stringBefore
= <span class="hljs-keyword">new</span> <span class="hljs-keyword">String</span>(is.readAllBytes());
<span class="hljs-keyword">String</span> stringAfter = stringBefore
.replaceAll(<span class="hljs-string">"\\n"</span>, <span class="hljs-string">""</span>)
.replaceAll(<span class="hljs-string">"-----BEGIN PUBLIC KEY-----"</span>, <span class="hljs-string">""</span>)
.replaceAll(<span class="hljs-string">"-----END PUBLIC KEY-----"</span>, <span class="hljs-string">""</span>)
.<span class="hljs-built_in">trim</span>();
<span class="hljs-built_in">byte</span>[] decoded = Base64
.getDecoder()
.decode(stringAfter);
KeySpec keySpec
= <span class="hljs-keyword">new</span> X509EncodedKeySpec(decoded);
publicKey = keyFactory.generatePublic(keySpec);
}
</code></pre>
<p>The <code>setPublicKey()</code> method instantiates a <code>PublicKey</code>. This method is nearly identical to the <code>setPrivateKey()</code> method, but let's take a look at the details.</p>
<p>The <code>ClassLoader</code> is used to get an <code>InputStream</code> to the public key file on the Class Path. The bytes of the file are read into a new <code>String</code>. Next, the <code>String</code> is processed as follows:</p>
<pre><code class="java"> String stringAfter = stringBefore
<span class="hljs-meta"> .replaceAll</span>(<span class="hljs-string">"\\n"</span>, <span class="hljs-string">""</span>)
<span class="hljs-meta"> .replaceAll</span>(<span class="hljs-string">"-----BEGIN PUBLIC KEY-----"</span>, <span class="hljs-string">""</span>)
<span class="hljs-meta"> .replaceAll</span>(<span class="hljs-string">"-----END PUBLIC KEY-----"</span>, <span class="hljs-string">""</span>)
<span class="hljs-meta"> .trim</span>()<span class="hljs-comment">;</span>
</code></pre>
<p>This processing is necessary because even though we used OpenSSL to generate a private key file with an <code>X509</code> encoding, this file is not directly usable by Java. If you try without the above processing, you'll get the following exception:</p>
<pre><code class="txt">java<span class="hljs-selector-class">.security</span><span class="hljs-selector-class">.spec</span><span class="hljs-selector-class">.InvalidKeySpecException</span>: java<span class="hljs-selector-class">.security</span><span class="hljs-selector-class">.InvalidKeyException</span>: invalid key format
</code></pre>
<p>The <code>X509EncodedKeySpec</code> class expects the public key to be a single line of text with all comments removed (Java Code Example..., n.d.). This is the reason for the processing.</p>
<p>After processing removes the newlines and comments, the <code>X509EncodedKeySpec</code> and <code>KeyFactory</code> are used to create the <code>PublicKey</code>.</p>
<pre><code class="java"><span class="hljs-attribute"> KeySpec keySpec</span>
= new X509EncodedKeySpec(decoded);
<span class="hljs-attribute"> publicKey</span> = keyFactory.generatePublic(keySpec);
</code></pre>
<p>We now have instances of <code>PrivateKey</code> and <code>PublicKey</code> which we created from the private and public key files generated by OpenSSL. So what do you think, want to start encrypting and decrypting? Let's do it!</p>
<h1 id="java-in-memory-test">Java In-Memory Test</h1>
<p>It's time to put things together and see if we can encrypt and decrypt a value. But we can't do this without the encryption and decryption methods. We need them first.</p>
<p>The following listings are snips from my <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> class. Look at the class on GitHub or read through the "Java KeyFactory, PrivateKey, PublicKey" section above for the full source of the class. The <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> class contains the encryption and decryption methods. Let's take a look at the encryption method first.</p>
<h2 id="encryption">Encryption</h2>
<h1 id="listing-5-1-encrypttobase64-method">Listing 5.1 - encryptToBase64() Method</h1>
<pre><code class="java"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">String</span> encryptToBase64(<span class="hljs-keyword">String</span> plainText) {
<span class="hljs-keyword">String</span> encoded = <span class="hljs-keyword">null</span>;
<span class="hljs-keyword">try</span> {
Cipher cipher = Cipher.getInstance(<span class="hljs-string">"RSA"</span>);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
<span class="hljs-built_in">byte</span>[] encrypted = cipher.doFinal(plainText.getBytes());
encoded = Base64.getEncoder().encodeToString(encrypted);
} <span class="hljs-keyword">catch</span> (Exception e) {
e.printStackTrace();
}
<span class="hljs-keyword">return</span> encoded;
}
</code></pre>
<p>Listing 5.1 shows the <code>encryptToBase64()</code> method. The method has one <code>String</code> parameter which is the value to be encrypted. Passing in a <code>byte[]</code> array may be more robust, but in my experience, the need is usually to encrypt <code>String</code> values. Of course, update for whatever meets your needs.</p>
<p>The name and return type of the method implies a <code>Base64</code> encoded String will be returned. Passing back a <code>byte[]</code> array may be more robust, but in my experience, a <code>String</code> return value is usually what's needed. Of course, update for whatever meets your needs.</p>
<p><strong>Only the <code>PublicKey</code> is needed for encryption.</strong></p>
<h2 id="decryption">Decryption</h2>
<h1 id="listing-5-2-decryptfrombase64-method">Listing 5.2 - decryptFromBase64() Method</h1>
<pre><code class="java"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">String</span> decryptFromBase64(<span class="hljs-keyword">String</span> base64EncodedEncryptedBytes) {
<span class="hljs-keyword">String</span> plainText = <span class="hljs-keyword">null</span>;
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">final</span> Cipher cipher = Cipher.getInstance(<span class="hljs-string">"RSA"</span>);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
<span class="hljs-built_in">byte</span>[] decoded = Base64
.getDecoder()
.decode(base64EncodedEncryptedBytes);
<span class="hljs-built_in">byte</span>[] decrypted = cipher.doFinal(decoded);
plainText = <span class="hljs-keyword">new</span> <span class="hljs-keyword">String</span>(decrypted);
} <span class="hljs-keyword">catch</span> (Exception ex) {
ex.printStackTrace();
}
<span class="hljs-keyword">return</span> plainText;
}
</code></pre>
<p>Listing 5.2 shows the decryptFromBase64() method. The method has one <code>String</code> parameter which by its name is a <code>Base64</code> encoded <code>String</code> of the encrypted <code>byte[]</code> array. Passing in a <code>byte[]</code> array may be more robust, but in my experience, the need is usually to decrypt a <code>String</code> back to it's original value. Of course, update for whatever meets your needs.</p>
<p>The name and return type of the method implies the original, <code>String</code> value will be returned. Passing back a <code>byte[]</code> array may be more robust, but in my experience, the original value is always a <code>String</code>. Of course, update for whatever meets your needs.</p>
<p><strong>Only the <code>PrivateKey</code> is needed for decryption.</strong></p>
<h2 id="unit-test">Unit Test</h2>
<p>Now let's take a look at the <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/test/java/org/thoth/rsa/InMemoryTest.java" title="InMemoryTest unit test"><code>InMemoryTest</code></a> unit test to see if everything works together. </p>
<blockquote>
<p><strong>NOTE</strong> In-memory encryption & decryption is <em>NOT</em> one of my goals. The goal is to encrypt with OpenSSL outside the application and decrypt with Java inside the application. However, trying in-memory first is a good test to make sure everything is working OK.</p>
</blockquote>
<h1 id="listing-5-3-inmemorytest-unit-test">Listing 5.3 - InMemoryTest Unit Test</h1>
<pre><code class="java"><span class="hljs-keyword">package</span> org.thoth.rsa;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Assertions;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.BeforeEach;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Test;
<span class="hljs-comment">/**
*
* <span class="hljs-doctag">@author</span> Michael Remijan mjremijan@yahoo.com <span class="hljs-doctag">@mjremijan</span>
*/</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InMemoryTest</span> </span>{
<span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">test_in_memory_encryption_decryption</span><span class="hljs-params">()</span>
<span class="hljs-keyword">throws</span> Exception
</span>{
<span class="hljs-comment">// Setup</span>
Rsa4096 rsa = <span class="hljs-keyword">new</span> Rsa4096(
<span class="hljs-string">"./private_key_rsa_4096_pkcs8-generated.pem"</span>
, <span class="hljs-string">"./public_key_rsa_4096_pkcs8-exported.pem"</span>
);
String expected
= <span class="hljs-string">"Text to be encrypted"</span>;
<span class="hljs-comment">// Test</span>
String encryptedAndEncoded
= rsa.encryptToBase64(expected);
String actual
= rsa.decryptFromBase64(encryptedAndEncoded);
<span class="hljs-comment">// Assert</span>
Assertions.assertEquals(expected, actual);
}
}
</code></pre>
<p>Listing 5.3 shows the <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/test/java/org/thoth/rsa/InMemoryTest.java" title="InMemoryTest unit test"><code>InMemoryTest</code></a> unit test. This test finally runs all the code and verifies a <code>String</code> can be encrypted and decrypted back to the same value.</p>
<p>First, the <code>// Setup</code> of the unit test specifies where to find the private and public key files. Remember, these files were generated by OpenSSL. I put them in the project's <code>src/test/resources/</code> directory so they would appear in the Class Path when the unit test runs. They are used to create an instance of my <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> class.</p>
<p>Next, the test does the encryption and decryption. Seems a bit anti-climatic, but all the work is in the <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> class. </p>
<p>Finally, the JUnit assertion checks the expected value equals the actual value. If all goes well, the test should pass meaning encrypting then decrypting returned the original value. Clone my <a href="https://github.com/mjremijan/thoth-rsa" title="thoth-rsa">thoth-rsa repository</a> and run the unit test for yourself to see that it works!</p>
<p>So the private and public keys generated by OpenSSL can be used <strong>within Java</strong> to encrypt and decrypt in-memory values. However, can a value be encrypted with OpenSSL <strong>outside</strong> of Java and yet be decrypted <strong>inside</strong> the application? Let's try it!</p>
<h1 id="encrypted-file">Encrypted File</h1>
<p>One of the stated goals of this research is for OpenSSL to encrypt an entire file and the Java application would decrypt it. It is very common for Java applications to externalize values into properties files. Although it may be better to encrypt only specific properties (which we will get to in the next section), encrypting the entire file is a quick and easy way to make sure no sensitive properties are missed. </p>
<p>To start, we need to encrypt an entire file. We already have the public key for encryption. So all that's left is the correct OpenSSL command. Let's take a look at the command.</p>
<h2 id="file-encryption">File Encryption</h2>
<h1 id="listing-6-1-openssl-encrypt-a-file">Listing 6.1 - OpenSSL Encrypt a File</h1>
<pre><code class="bash">openssl rsautl -encrypt -inkey public_key_rsa_4096_pkcs8-exported.pem -pubin -<span class="hljs-built_in">in</span> file_unencrypted.txt | <span class="hljs-type">openssl</span> enc -A -base64 > file_encrypted_and_encoded.txt
</code></pre>
<p>Listing 6.1 (admin. 2018) shows the OpenSSL command to both encrypt and <code>Base64</code> encode the contents of a plain text file into a new file. Remember, when encrypting, only the public key file is needed. Thus separation of responsibilities can be maintained while handling sensitive data. The <code>file_encrypted_and_encoded.txt</code> file created by this command contain a <code>Base64</code> encoded string that looks something like this:</p>
<pre><code class="txt">UwXBjowtfDQix2lOiBbaX6J8GayYmo5EsZuHxPUtS+MW9kncnVNpeWw+jpOc1yEiSanFEeRE4QQz<span class="hljs-regexp">/DKWr16LHAt4B8OMOSvXikEpnv0uvr+UtKTE1KalHZDKBHvk5op44gMhhQVpyjKQrVMY/</span><span class="hljs-number">76</span>R83o0<span class="hljs-regexp">/kj60fNsuqpx5DIH/</span>RHhnwBCNvjpjlsvLPPlL1YqUIn0i+t+<span class="hljs-number">5</span>XCaZcTiJhpsOh2LmEhfARLgMqVGZxb0zIPvn0zPerhVSZK1wUcI4Va+nOj2rDOflL1Sr5eiimAaIC5<span class="hljs-regexp">/zZniIZP4RDdF3VvlMur5MzUkgxM8CkIJPxKUj8QsEPEcVt3p3/</span>cIvR9YeBmP6Gsw78NutJH3vXAvduPIB2<span class="hljs-regexp">/z/</span>w8iRn<span class="hljs-regexp">/NYcCRX8xZUEGcM44Ks1n7eT+pUWJE1T+3KfH08HOhXuMJUocaxSiZiX2ROQt/g</span>KPJsz27b3u967y9s1DozaaJY+<span class="hljs-number">1</span>nKOqEbHDg<span class="hljs-regexp">/uVcgmwYXD5CDy+/</span>qAqKXRJ3dCmJWw46OwPSTMAhkBGOihDhrcQbid3O9rsTU<span class="hljs-regexp">/Od19Fa+OGnS55HHv/</span><span class="hljs-number">4</span>cnIwJnKXBtziG5EaJlouu<span class="hljs-regexp">/H+poabQEoiwgcuh2OOj41Rm6nG3Ef3uxppdoXCn9x3wMDHlqc8K+0Nenc2IbAM/</span><span class="hljs-regexp">/Vd98PVwBf5/</span>nvNyQKwfpQOFJrT4Ygyt3qWQ00cLG7u3fsngg0=
</code></pre>
<p>Great! Encrypted file; check! Now here's the big question: Can Java decrypt it? Let's find out!</p>
<h1 id="unit-test">Unit Test</h1>
<p>Let's have a look at the <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/test/java/org/thoth/rsa/EncryptedFileTest.java" title="EncryptedFileTest unit test"><code>EncryptedFileTest</code></a> unit test.</p>
<h1 id="listing-6-2-encryptedfiletest-unit-test">Listing 6.2 - EncryptedFileTest Unit Test</h1>
<pre><code class="java"><span class="hljs-keyword">package</span> org.thoth.rsa;
<span class="hljs-keyword">import</span> java.io.InputStream;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Assertions;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.BeforeEach;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Test;
<span class="hljs-comment">/**
*
* <span class="hljs-doctag">@author</span> Michael Remijan mjremijan@yahoo.com <span class="hljs-doctag">@mjremijan</span>
*/</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EncryptedFileTest</span> </span>{
<span class="hljs-keyword">protected</span> Rsa4096 rsa;
<span class="hljs-meta">@BeforeEach</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setUp</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
rsa = <span class="hljs-keyword">new</span> Rsa4096(
<span class="hljs-string">"./private_key_rsa_4096_pkcs8-generated.pem"</span>
, <span class="hljs-string">"./public_key_rsa_4096_pkcs8-exported.pem"</span>
);
}
<span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">test_encrypted_file</span><span class="hljs-params">()</span>
<span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-comment">// Setup</span>
String expected
= getFileAsString(<span class="hljs-string">"./file_unencrypted.txt"</span>);
String encryptedAndEncoded
= getFileAsString(<span class="hljs-string">"./file_encrypted_and_encoded.txt"</span>);
<span class="hljs-comment">// Test</span>
String actual
= rsa.decryptFromBase64(encryptedAndEncoded);
System.out.printf(<span class="hljs-string">"%s%n"</span>, actual);
<span class="hljs-comment">// Assert</span>
Assertions.assertEquals(expected, actual);
}
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getFileAsString</span><span class="hljs-params">(String classPathResourceLocation)</span>
<span class="hljs-keyword">throws</span> Exception </span>{
InputStream is = <span class="hljs-keyword">this</span>
.getClass()
.getClassLoader()
.getResourceAsStream(
classPathResourceLocation
);
<span class="hljs-keyword">byte</span>[] bytes = is.readAllBytes();
is.close();
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> String(bytes);
}
}
</code></pre>
<p>First, the <code>@BeforeEach</code> method creates an instance of my <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> class. This uses the private and public key files generated by OpenSSL. These key files are on the Java Class Path when the unit test runs. <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> is used to decode and decrypt the contents of the encrypted file.</p>
<p>Second, the <code>getFileAsString()</code> helper method is called. The name of the method tells exactly what it does. It finds a file on the Java Class Path and reads its contents into a <code>String</code>. Remember, the OpenSSL file encryption command both encrypted and <code>Base64</code> encoded the contents of the output file, so it's safe the store those contents as a <code>String</code>.</p>
<p>Third, <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> is used to decode and decrypt by calling <code>decryptFromBase64()</code>.</p>
<p>Finally, the JUnit assertions make sure decoding and decryption were successful and that the test got back the original value.</p>
<p>That's it. We did it! But that's not all. Sure, encrypting an entire file is fun, but what's even more fun is encrypting only specific values within the file. There is no way this can be done...or can it? Let's see.</p>
<h1 id="encrypted-values-in-a-file">Encrypted Values in a File</h1>
<p>Another goal of this research is to use OpenSSL to encrypt only specific values within a file. For this to work, there must be a starting template file containing placeholders for variable replacement. They will be replaced with encrypted and encoded values. OpenSSL will be used for the encryption and encoding, but we'll also need to pipe in <code>sed</code> for the search and replace. Let's take a look.</p>
<h2 id="value-encryption">Value Encryption</h2>
<h1 id="listing-7-1-openssl-encrypts-values-in-a-file">Listing 7.1 - OpenSSL Encrypts Values in a File</h1>
<pre><code class="bash">sed <span class="hljs-string">"s|XXXX|`printf "</span>SECRET<span class="hljs-string">" | openssl rsautl -encrypt -inkey public_key_rsa_4096_pkcs8-exported.pem -pubin | openssl enc -A -base64`|g"</span> some_template<span class="hljs-selector-class">.properties</span> > some_tmp1<span class="hljs-selector-class">.properties</span>
sed <span class="hljs-string">"s|YYYY|`printf "</span><span class="hljs-number">123</span>-<span class="hljs-number">45</span>-<span class="hljs-number">7890</span><span class="hljs-string">" | openssl rsautl -encrypt -inkey public_key_rsa_4096_pkcs8-exported.pem -pubin | openssl enc -A -base64`|g"</span> some_tmp1<span class="hljs-selector-class">.properties</span> > some_app.properties
</code></pre>
<p>Listing 7.1 gets a little out there with piping Unix commands so let's take a look at this in small pieces.</p>
<p>First, start with the <code>some_template.properties</code> file. This is a standard Java properties file but some of the properties in the file do not have values, they have placeholders for variable replacement:</p>
<pre><code class="properties"><span class="hljs-attr">name</span>=mike
<span class="hljs-attr">color</span>=blue
<span class="hljs-attr">password</span>=XXXX
<span class="hljs-attr">size</span>=L
<span class="hljs-attr">ssn</span>=YYYY
<span class="hljs-attr">price</span>=<span class="hljs-number">4.99</span>
</code></pre>
<p>As you can see, <code>password</code> and <code>ssn</code> have placeholders for encrypted sensitive information. XXXX and YYYY should be replaced.</p>
<p>Second, the <code>sed "s|XXXX|`printf "SECRET"</code> part of the command will obviously do a search and replace of <code>XXXX</code> with the plain text <code>SECRET</code>. What's important to note is that since these commands are all pipped to each other, the sensitive text is never written to a file. </p>
<p>Third, the output file is <code>some_tmp1.properties</code>. This file is appropriately named because it is only <strong>temporary</strong>. The template has two values needing replacement. The first command only does the search and replace on <code>XXXX</code>. The <strong>temporary</strong> file will look like this:</p>
<pre><code class="properties"><span class="hljs-attr">name</span>=mike
<span class="hljs-attr">color</span>=blue
<span class="hljs-attr">Password</span>=sh3kiZTGtvcPlY3eqnUSkIC+HplryBs....=
<span class="hljs-attr">size</span>=L
<span class="hljs-attr">ssn</span>=YYYY
<span class="hljs-attr">price</span>=<span class="hljs-number">4.99</span>
</code></pre>
<p>Fourth, the second command has <code>sed "s|YYYY|`printf "123-45-7890"</code> and the input file is <code>some_tmp1.properties</code>. The output is written to <code>some_app.properties</code>. The <code>some_app.properties</code> file is now ready to be used by the application because all sensitive data has been encrypted, encoded, and placed within the file. The <code>some_app.properties</code> now looks like:</p>
<pre><code class="properties"><span class="hljs-attr">name</span>=mike
<span class="hljs-attr">color</span>=blue
<span class="hljs-attr">Password</span>=sh3kiZTGtvcPlY3eqnUSk....=
<span class="hljs-attr">size</span>=L
<span class="hljs-attr">ssn</span>=trpmRDvKnnjuT6hZvObthguN3A....=
<span class="hljs-attr">price</span>=<span class="hljs-number">4.99</span>
</code></pre>
<h2 id="unit-test">Unit Test</h2>
<p><a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/test/java/org/thoth/rsa/EncryptedValuesInPropertiesFileTest.java" title="EncryptedValuesInPropertiesFileTest unit test"><code>EncryptedValuesInPropertiesFileTest</code></a> is the last unit test we'll look at. </p>
<h1 id="listing-7-2-encryptedvaluesinpropertiesfiletest-unit-test">Listing 7.2 - EncryptedValuesInPropertiesFileTest Unit Test</h1>
<pre><code class="java"><span class="hljs-keyword">package</span> org.thoth.rsa;
<span class="hljs-keyword">import</span> java.util.Properties;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Assertions;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.BeforeEach;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Test;
<span class="hljs-comment">/**
*
* <span class="hljs-doctag">@author</span> Michael Remijan mjremijan@yahoo.com <span class="hljs-doctag">@mjremijan</span>
*/</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EncryptedValuesInPropertiesFileTest</span> </span>{
<span class="hljs-keyword">protected</span> Rsa4096 rsa;
<span class="hljs-meta">@BeforeEach</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setUp</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
rsa = <span class="hljs-keyword">new</span> Rsa4096(
<span class="hljs-string">"./private_key_rsa_4096_pkcs8-generated.pem"</span>
, <span class="hljs-string">"./public_key_rsa_4096_pkcs8-exported.pem"</span>
);
}
<span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">test_encrypted_values_in_properties_file</span><span class="hljs-params">()</span>
<span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-comment">// Setup</span>
Properties encryptedAndEncoded
= <span class="hljs-keyword">new</span> Properties();
encryptedAndEncoded.load(
<span class="hljs-keyword">this</span>
.getClass()
.getClassLoader()
.getResourceAsStream(
<span class="hljs-string">"./some_app.properties"</span>
)
);
<span class="hljs-comment">// Test</span>
String passwordActual
= rsa.decryptFromBase64(
encryptedAndEncoded.getProperty(<span class="hljs-string">"password"</span>)
);
String ssnActual
= rsa.decryptFromBase64(
encryptedAndEncoded.getProperty(<span class="hljs-string">"ssn"</span>)
);
<span class="hljs-comment">// Assert</span>
Assertions.assertEquals(<span class="hljs-string">"SECRET"</span>, passwordActual);
Assertions.assertEquals(<span class="hljs-string">"123-45-7890"</span>, ssnActual);
}
}
</code></pre>
<p>Listing 7.2 shows the <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/test/java/org/thoth/rsa/EncryptedValuesInPropertiesFileTest.java" title="EncryptedValuesInPropertiesFileTest unit test"><code>EncryptedValuesInPropertiesFileTest</code></a> unit test. The test reads in the <code>some_app.properties</code> file and hopefully it is able to decode and decrypt the values within it.</p>
<p>First, the <code>@BeforeEach</code> method creates an instance of my <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> class. This uses the private and public key files generated by OpenSSL. These key files are on the Java Class Path when the unit test runs. <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> is used to decode and decrypt the contents of the encrypted file.</p>
<p>Second, a <code>Properties</code> object is created and <code>load()</code> is called to load it with the contents of the properties file. Remember, the <code>some_app.properties</code> file is found on the the Class Path. </p>
<p>Third, the encrypted and encoded values are retrieved from the <code>Properties</code> object and then <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> is used to decode and decrypt those values them by calling <code>decryptFromBase64()</code>.</p>
<p>Finally, the JUnit assertions make sure decoding and decryption were successful and that the test got back the original value.</p>
<p>That's it. We did it! All of the goals we set out to achieve have been accomplished. Just to make sure, let's review.</p>
<h1 id="summary">Summary</h1>
<p>The purpose of this blog is to demonstrate Java's interoperability with OpenSSL: </p>
<ul>
<li>Generate private and public keys with OpenSSL</li>
<li>Encrypt values with OpenSSL</li>
<li>Decrypt values with Java</li>
</ul>
<p>I was able to demonstrate this by defining and accomplishing the following goals:</p>
<p><strong>Encryption tool of choice is OpenSSL. It is on every Linux/Unix system, is an industry standard, and will be familiar to all DevOps teams.</strong> I demonstrated OpenSSL commands to perform all needed operations. For cases <code>openssl</code> could not do everything on its own, the command was piped to other standard Linux/Unix tools like <code>sed</code>.</p>
<p><strong>Encryption performed by DevOps, or another team, so there is a separation of responsibilities. No one on the development team may know an unencrypted value.</strong> I demonstrated this showing separate commands for generating private and public key files and for encrypting files or values. Being separate commands, there can be separation of responsibilities if required.</p>
<p><strong>All environments will use their own keys. No key sharing.</strong> I demonstrated this by showing how easy it is to execute the commands for generating keys. These commands may even be automated by an infrastructure as code process for each environment.</p>
<p><strong>All keys and encrypted values may be regenerated at any time with no change to the application.</strong> Maven can easily add files to the Class Path when running unit tests and I took advantage of this developing my tests. I hope it's clear that even if you use the Class Path strategy as I did, it is trivial to regenerate all keys and encrypted values. A restart the application will read everything anew. No changes to the application are needed. Keep in mind it is possible for you to create your own strategy and write code to support that strategy that also makes the "no changes" goal impossible...try not to do that :)</p>
<p><strong>Encryption will be either of an entire file or of specific values within a (properties) file.</strong> I demonstrated this with the OpenSSL commands to do both. I also provide the <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/test/java/org/thoth/rsa/EncryptedFileTest.java" title="EncryptedFileTest unit test"><code>EncryptedFileTest</code></a> and the <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/test/java/org/thoth/rsa/EncryptedValuesInPropertiesFileTest.java" title="EncryptedValuesInPropertiesFileTest unit test"><code>EncryptedValuesInPropertiesFileTest</code></a> unit tests to prove it works.</p>
<p><strong>Encrypted values and keys are made available to the Java runtime using a strategy agreed upon and enforced by both DevOps and Development teams.</strong> I demonstrated this by deciding my code would take advantage of Maven's ability to put files on the Class Path. Therefore, my strategy is reading the files from the Class Path. Of course you can decide on your own strategy and update the code to support it.</p>
<p><strong>Decryption is performed by the Java application for whatever purposes it needs. Don't log the encrypted values!</strong> I demonstrated this with the <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> class which performs the decoding and decryption. Also - and this is very important - I never log any of the decoded and decrypted values in either the <a href="https://github.com/mjremijan/thoth-rsa/blob/master/src/main/java/org/thoth/rsa/Rsa4096.java" title="Rsa4096 class"><code>Rsa4096</code></a> class or in the unit tests.</p>
<p>That's it! Thanks for taking this journey with me. This was a fun topic of research and I hope you have found some value in reading through this. Email me or leave a comment and let me know.</p>
<h1 id="references">References</h1>
<p>Remijan, M. (2017, December 22). Choosing Java Cryptographic Algorithms Part 3 - Public/Private key asymmetric encryption. Retrieved from <a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_5.html">http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_5.html</a>.</p>
<p>Java Code Examples for <em>java.security.PrivateKey</em>. (n.d.) Retrieved from <a href="http://www.javased.com/index.php?api=java.security.PrivateKey">http://www.javased.com/index.php?api=java.security.PrivateKey</a></p>
<p>destan. (2017, October 1). ParseRSAKeys.java. Retrieved from <a href="https://gist.github.com/destan/b708d11bd4f403506d6d5bb5fe6a82c5">https://gist.github.com/destan/b708d11bd4f403506d6d5bb5fe6a82c5</a></p>
<p>admin. (2018, August 21). Using OpenSSL to encrypt messages and files on Linux. Retrieved from <a href="https://linuxconfig.org/using-openssl-to-encrypt-messages-and-files-on-linux">https://linuxconfig.org/using-openssl-to-encrypt-messages-and-files-on-linux</a></p>
<p>Java Code Examples for java.security.spec.PKCS8EncodedKeySpec. (n.d.) Retrieved from <a href="https://www.programcreek.com/java-api-examples/java.security.spec.PKCS8EncodedKeySpec">https://www.programcreek.com/java-api-examples/java.security.spec.PKCS8EncodedKeySpec</a></p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-38704814788122298462020-02-28T10:05:00.001-06:002020-02-28T10:27:04.214-06:00Explode a WAR File Recursively <h1 id="abstract">Abstract</h1>
<p>Ever need to explode a WAR file as well as exploding all JAR files in the WAR file? Ya, me too!</p>
<p>I wrote ferris-war-exploder to explode either:</p>
<ol>
<li>A JAR file</li>
<li>A WAR file which every JAR file it finds also exploded.</li>
<li>An EAR file with every JAR file (see #1) and WAR file (see #2) also exploded.</li>
</ol>
<p>Basically, ferris-war-exploder explodes anything which is a ZIP file format. Any entries which are in a ZIP file format will also be exploded. This happens recursively so anything that can be exploded is exploded.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>NetBeans 11.2</li>
<li>Maven 3.3.9 (Bundled with NetBeans)</li>
<li>Java 11 (zulu11.35.15-ca-jdk11.0.5-win_x64)</li>
</ul>
<h1 id="download">Download</h1>
<p>Visit my GitHub page <a href="https://github.com/mjremijan">https://github.com/mjremijan</a> to see all of my open source projects. The code for this post is located at: <a href="https://github.com/mjremijan/ferris-war-exploder" title="ferris-war-exploder">https://github.com/mjremijan/ferris-war-exploder</a></p>
<h1 id="letsgettoit">Let’s get to it</h1>
<p>ferris-war-exploder explodes anything which is a ZIP file format. Any entries which are in a ZIP file format will also be exploded. This happens recursively so anything that can be exploded is exploded.</p>
<p>YOU need to tell it the archive (WAR, JAR, EAR, ZIP) to explode.</p>
<p>YOU need to tell it where to explode the archive.</p>
<blockquote>
<p><strong>NOTE</strong>
See my <a href="https://github.com/mjremijan/ferris-magic-number" title="ferris-magic-number">ferris-magic-number</a> to analyze all of the <code>.class</code> files once the WAR is exploded.</p>
</blockquote>
<p>Listing 1 shows the <code>main()</code> method to start the application. I have 2 examples: Exploding a JAR and exploding a WAR.</p>
<h1 id="listing1-themainmethod">Listing 1 - The <code>main()</code> method</h1>
<pre><code class="Java">public class Main {
public static void main(String[] args) throws Exception
{
System.out.printf("=== Welcome to Ferris WAR Exploder ===%n");
new Unzip("./src/test/jars/commons-lang3-3.7.jar", "./target/unzipped/jar")
.unzip();
new Unzip("./src/test/wars/sample.war", "./target/unzipped/war")
.unzip();
System.out.printf("%n=== DONE ===%n");
}
}
</code></pre>
<p>Listing 2 shows the <code>Unzip</code> class. This class contains the interesting code to recursively explode an archive. Nothing in Listing 2 is difficult to understand, so I’ll leave it up to you to read through.</p>
<h1 id="listing2-theunzipmethod">Listing 2 - The <code>Unzip</code> method</h1>
<pre><code class="java">package org.ferris.war.exploder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class Unzip {
protected File zipFile;
protected File destinationDirectory;
public Unzip(String zipFilePath, String destinationDirectoryPath) {
setZipFile(zipFilePath);
setDestinationDirectory(destinationDirectoryPath);
}
public Unzip(File zipFile) {
this.zipFile = zipFile;
setDestinationDirectory(zipFile.getParent());
}
protected void setDestinationDirectory(String destinationDirectoryPath) {
destinationDirectory = new File(destinationDirectoryPath, zipFile.getName());
if (destinationDirectory.exists() && destinationDirectory.isDirectory()) {
throw new RuntimeException(
String.format(
"The destination directory \"%s\" already exists.",
destinationDirectory.getPath()
)
);
}
if (destinationDirectory.exists() && destinationDirectory.isFile()) {
destinationDirectory = new File(destinationDirectoryPath, zipFile.getName() + ".d");
}
mkdirs(destinationDirectory,
"Failed to create the destination directory \"%s\"."
);
}
protected void setZipFile(String zipFilePath) {
zipFile = new File(zipFilePath);
if (!zipFile.exists()) {
throw new RuntimeException(
String.format(
"The file \"%s\" does not exist", zipFile.getPath()
)
);
}
if (!zipFile.canRead()) {
throw new RuntimeException(
String.format(
"The file \"%s\" is not readable", zipFile.getPath()
)
);
}
}
protected void unzip() throws Exception {
System.out.printf("%n=== Unipping %s ===%n%n", zipFile.getPath());
try (ZipInputStream zip
= new ZipInputStream(new FileInputStream(zipFile));
){
for (ZipEntry z = zip.getNextEntry(); z != null; z = zip.getNextEntry()) {
if (z.isDirectory()) {
mkdirs(new File(destinationDirectory, z.getName()),
"Failed to create a zip entry directory \"%s\""
);
} else {
File zfile = new File(destinationDirectory, z.getName());
mkdirs(zfile.getParentFile(),
"Failed to create parent directory for zip entry file \"%s\"."
);
File unzippedFile = unzipEntry(z, zip);
if (isZip(unzippedFile)) {
new Unzip(unzippedFile).unzip();
}
}
}
}
}
protected boolean isZip(File file) {
boolean b = false;
try {
b = new ZipFile(file).getName().length() > 0;
} catch (IOException ignore) {}
return b;
}
protected File unzipEntry(ZipEntry z, ZipInputStream zip) throws Exception {
File zfile = new File(destinationDirectory, z.getName());
System.out.printf(" %s%n", zfile.getAbsolutePath());
try ( FileOutputStream out = new FileOutputStream(zfile)) {
zip.transferTo(out);
}
zip.closeEntry();;
return zfile;
}
protected void mkdirs(File dir, String errorMessageFormat) {
if (dir.exists() && dir.isDirectory()) {
return;
}
dir.mkdirs();
if (!dir.exists()) {
throw new RuntimeException(
String.format(errorMessageFormat, dir.getPath()
)
);
}
}
}
</code></pre>
<h1 id="summary">Summary</h1>
<p>The ferris-war-exploder project isn’t too complicated, but it is very handy when you need to completely explode a WAR or EAR archive. Enjoy!</p>
<h1 id="references">References</h1>
<p>ZipOutputStream. (n.d.). Oracle. Retrieved from <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/zip/ZipOutputStream.html">https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/zip/ZipOutputStream.html</a>.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-87662398963530790462020-02-25T18:31:00.000-06:002020-02-25T18:31:54.030-06:00 Discovering the Magic Version Number of .class Files<h1 id="abstract">Abstract</h1>
<p>What version of Java was used to create your <code>.class</code> files? For your own projects, this is easy to figure out because you choose your Java version when you compile. For the hundreds or thousands of dependencies in your project, this question is not so easy to answer. Inside every <code>.class</code> file is a magic number with a <code>byte</code> value indicating the Java version used to create the <code>.class</code> file. This blog describes some code in <a href="https://github.com/mjremijan/ferris-magic-number" title="ferris-magic-number">ferris-magic-number</a> which analyzes and reports the Java versions on a directory full of <code>.class</code> files.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>NetBeans 11.2</li>
<li>Maven 3.3.9 (Bundled with NetBeans)</li>
<li>Java 11 (zulu11.35.15-ca-jdk11.0.5-win_x64)</li>
</ul>
<h1 id="download">Download</h1>
<p>Visit my GitHub page <a href="https://github.com/mjremijan">https://github.com/mjremijan</a> to see all of my open source projects. The code for this post is located at: <a href="https://github.com/mjremijan/ferris-magic-number" title="ferris-magic-number">https://github.com/mjremijan/ferris-magic-number</a></p>
<h1 id="letsgettoit">Let’s get to it</h1>
<p><a href="https://github.com/mjremijan/ferris-magic-number" title="ferris-magic-number">ferris-magic-number</a> searches an entire directory tree for files that end with <code>.class</code>. It will analyze the Magic Number version number byte value of all the classes and print out a detailed and summary report (<code>System.out.printf()</code>…nothing too complicated).</p>
<p>YOU need to tell it the directory to search.</p>
<p>YOU need to unzip your JAR files.</p>
<blockquote>
<p><strong>NOTE</strong>
See my <a href="https://github.com/mjremijan/war-exploder" title="ferris-war-exploder">ferris-war-exploder</a> project to easily explode a WAR file along with every JAR file within the WAR file.</p>
</blockquote>
<p>Listing 1 shows the <code>main()</code> method to start the application. Update the path appropriately.</p>
<h1 id="listing1-themainmethod">Listing 1 - The <code>main()</code> method</h1>
<pre><code class="Java">package org.ferris.magic.number;
import java.nio.file.Paths;
public static void main(String[] args) throws Exception
{
System.out.printf(
"=== Welcome to Ferris Magic Number ===%n"
);
MagicNumbers.load(
Paths.get("./src/test/jars")
).print();
System.out.printf("%n=== DONE ===%n");
}
</code></pre>
<p>Listing 2 shows the <code>MagicNumber</code> class. This class contains the interesting code parsing the <code>.class</code> file and reading the major version byte value. Nothing in Listing 2 is difficult to understand, so I’ll leave it up to you to read through.</p>
<h1 id="listing2-themagicnumbermethod">Listing 2 - The <code>MagicNumber</code> method</h1>
<pre><code class="java">package org.ferris.magic.number;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Arrays;
public class MagicNumber {
protected Path classFile;
protected byte[] CAFEBABEs, minors, majors;
protected Major major;
public MagicNumber (Path classFile) throws IOException {
try (
InputStream is = new FileInputStream(classFile.toFile());
) {
this.classFile = classFile;
// Magic number info:
// https://en.wikipedia.org/wiki/Java_class_file#Magic_Number
CAFEBABEs = new byte[4];
is.read(CAFEBABEs);
minors = new byte[2];
is.read(minors);
majors = new byte[2];
is.read(majors);
major = new Major(majors[1]);
}
}
@Override
public String toString() {
StringBuilder sp = new StringBuilder();
sp.append(String.format(
" CLASS: %s%n", classFile.toString()));
sp.append(String.format(
" minor[] = %s%n", Arrays.toString(minors)));
sp.append(String.format(
" major[] = %s%n", Arrays.toString(majors)));
sp.append(String.format(
" major = %s%n", major.toString()));
return sp.toString();
}
public Major getMajor() {
return major;
}
}
</code></pre>
<h1 id="exampleoutput">Example Output</h1>
<p>What does the output look like? See Listing 4. That’s the entire output and you’ll need to scroll a little bit :) The output in Listing 4 is the from the example <code>.class</code> files that come with the project. So if you clone the repository and execute as-is, you’ll get this output of Listing 4.</p>
<p>What may be more useful is Listing 3, which shows just the summary output (scroll all the way to the bottom of Listing 4 to see the summary). Listing 3 shows how ferris-magic-number shows a breakdown of how many different Java versions were found and how many <code>.class</code> files for each version.</p>
<h1 id="listing3-summaryoutput">Listing 3 - Summary Output</h1>
<pre><code class="txt">=== SUMMARY ===
Different major version count: 2
'JDK 1.3 = 47' class count: 118
'JDK 1.1 = 45' class count: 333
=== DONE ===
</code></pre>
<h1 id="listing4-fulloutput">Listing 4 - Full Output</h1>
<pre><code class="txt">=== Welcome to Ferris Magic Number ===
=== 47 ===
Count: 118
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\BasicDynaBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\BasicDynaClass.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\BeanAccessLanguageException.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\BeanUtils.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\BeanUtilsBean$1.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\BeanUtilsBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\BeanUtilsBean2.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\ConstructorUtils.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\ContextClassLoaderLocal.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\ConversionException.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\Converter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\AbstractArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\AbstractConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\ArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\BigDecimalConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\BigIntegerConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\BooleanArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\BooleanConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\ByteArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\ByteConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\CalendarConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\CharacterArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\CharacterConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\ClassConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\ConverterFacade.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\DateConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\DateTimeConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\DoubleArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\DoubleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\FileConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\FloatArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\FloatConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\IntegerArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\IntegerConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\LongArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\LongConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\NumberConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\ShortArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\ShortConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\SqlDateConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\SqlTimeConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\SqlTimestampConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\StringArrayConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\StringConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\converters\URLConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\ConvertingWrapDynaBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\ConvertUtils.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\ConvertUtilsBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\ConvertUtilsBean2.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\DynaBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\DynaBeanMapDecorator$MapEntry.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\DynaBeanMapDecorator.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\DynaClass.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\DynaProperty.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\expression\DefaultResolver.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\expression\Resolver.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\JDBCDynaClass.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\LazyDynaBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\LazyDynaClass.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\LazyDynaList.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\LazyDynaMap.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\BaseLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\BigDecimalLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\BigIntegerLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\ByteLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\DateLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\DecimalLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\DoubleLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\FloatLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\IntegerLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\LongLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\ShortLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\SqlDateLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\SqlTimeLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\SqlTimestampLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\converters\StringLocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleBeanUtils$Descriptor.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleBeanUtils.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleBeanUtilsBean$1.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleBeanUtilsBean$Descriptor.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleBeanUtilsBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleConverter.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleConvertUtils.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleConvertUtilsBean$1.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleConvertUtilsBean$DelegateFastHashMap.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\locale\LocaleConvertUtilsBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\MappedPropertyDescriptor$MappedMethodReference.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\MappedPropertyDescriptor.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\MethodUtils$MethodDescriptor.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\MethodUtils.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\MutableDynaClass.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\NestedNullException.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\PropertyUtils.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\PropertyUtilsBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\ResultSetDynaClass.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\ResultSetIterator.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\RowSetDynaClass.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WeakFastHashMap$1.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WeakFastHashMap$CollectionView$CollectionViewIterator.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WeakFastHashMap$CollectionView.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WeakFastHashMap$EntrySet.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WeakFastHashMap$KeySet.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WeakFastHashMap$Values.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WeakFastHashMap.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WrapDynaBean.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WrapDynaClass$1.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WrapDynaClass$2.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\beanutils\WrapDynaClass.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\ArrayStack.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\Buffer.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\BufferUnderflowException.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\FastHashMap$1.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\FastHashMap$CollectionView$CollectionViewIterator.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\FastHashMap$CollectionView.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\FastHashMap$EntrySet.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\FastHashMap$KeySet.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\FastHashMap$Values.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
CLASS: .\src\test\jars\commons-beanutils-core-1.8.3\org\apache\commons\collections\FastHashMap.class
minor[] = [0, 0]
major[] = [0, 47]
major = 'JDK 1.3 = 47'
=== 45 ===
Count: 333
CLASS: .\src\test\jars\dom4j-1.1\com\werken\saxpath\DefaultXPathHandler$Singleton.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\com\werken\saxpath\DefaultXPathHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\com\werken\saxpath\Token.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\com\werken\saxpath\TokenTypes.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\com\werken\saxpath\XPathLexer.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\com\werken\saxpath\XPathReader.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Attribute.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\bean\BeanAttribute.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\bean\BeanAttributeList.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\bean\BeanDocumentFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\bean\BeanElement.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\bean\BeanMetaData.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Branch.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\CDATA.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\CharacterData.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Comment.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\datatype\DatatypeAttribute.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\datatype\DatatypeDocumentFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\datatype\DatatypeElement.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\datatype\DatatypeElementFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\datatype\InvalidSchemaException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\datatype\NamedTypeResolver.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\datatype\SchemaParser.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Document.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\DocumentException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\DocumentFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\DocumentHelper.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\DocumentType.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMAttribute.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMAttributeNodeMap.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMCDATA.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMComment.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMDocument.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMDocumentFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMDocumentType.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMElement.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMEntityReference.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMNamespace.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMNodeHelper$1.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMNodeHelper$EmptyNodeList.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMNodeHelper.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMProcessingInstruction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dom\DOMText.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dtd\AttributeDecl.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dtd\ElementDecl.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dtd\ExternalEntityDecl.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\dtd\InternalEntityDecl.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Element.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\ElementHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\ElementPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Entity.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\IllegalAddException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\InvalidXPathException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\aelfred\DefaultHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\aelfred\SAXDriver$Adapter.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\aelfred\SAXDriver.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\aelfred\XmlParser.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\DispatchHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\DocumentInputSource$1.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\DocumentInputSource.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\DocumentResult.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\DocumentSource.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\DOMReader.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\DOMWriter.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\ElementStack.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\HTMLWriter.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\JAXPHelper.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\OutputFormat.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\PruningElementStack.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\SAXContentHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\SAXHelper.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\SAXReader$SAXEntityResolver.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\SAXReader.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\SAXValidator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\SAXWriter.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\XMLResult.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\XMLWriter.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\io\XPPReader.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Namespace.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Node.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\NodeFilter.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\ProcessingInstruction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\QName.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\Action.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\Mode.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\NullAction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\pattern\DefaultPattern.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\pattern\NodeTypePattern.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\Pattern.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\Rule.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\RuleManager$1.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\RuleManager.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\RuleSet.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\rule\Stylesheet.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\swing\BranchTreeNode$1.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\swing\BranchTreeNode.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\swing\DocumentTreeModel.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\swing\LeafTreeNode$1.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\swing\LeafTreeNode.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\swing\XMLTableColumnDefinition.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\swing\XMLTableDefinition.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\swing\XMLTableModel.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Text.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractAttribute.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractBranch.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractCDATA.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractCharacterData.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractComment.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractDocument.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractDocumentType.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractElement.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractEntity.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractNode.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractProcessingInstruction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\AbstractText.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\BackedList.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\BaseElement.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\ContentListFacade.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultAttribute.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultCDATA.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultComment.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultDocument.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultDocumentType.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultElement.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultEntity.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultNamespace.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultProcessingInstruction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\DefaultText.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\ElementIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\ElementNameIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\ElementQNameIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\FilterIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\FlyweightAttribute.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\FlyweightCDATA.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\FlyweightComment.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\FlyweightEntity.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\FlyweightProcessingInstruction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\FlyweightText.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\NamespaceCache.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\NamespaceStack.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\QNameCache.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\tree\SingleIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\AttributeHelper.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\IndexedDocumentFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\IndexedElement.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\NodeComparator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\NonLazyDocumentFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\NonLazyElement.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\ProxyDocumentFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\UserDataAttribute.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\UserDataDocumentFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\UserDataElement.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\util\XMLErrorHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\Visitor.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\VisitorSupport.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\xpath\DefaultNamespaceContext.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\xpath\DefaultXPath$1.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\xpath\DefaultXPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\xpath\XPathPattern.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\XPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\XPathException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\dom4j\xpp\ProxyXmlStartTag.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\BaseXPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\Context.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\ContextSupport.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\DefaultNavigator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\dom4j\DocumentNavigator$Singleton.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\dom4j\DocumentNavigator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\dom4j\XPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\BinaryExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultAbsoluteLocationPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultAdditiveExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultAllNodeStep.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultAndExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultArithExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultBinaryExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultCommentNodeStep.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultDivExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultEqualityExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultEqualsExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultFilterExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultFunctionCallExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultGreaterThanEqualExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultGreaterThanExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultLessThanEqualExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultLessThanExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultLiteralExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultLocationPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultLogicalExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultMinusExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultModExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultMultiplicativeExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultMultiplyExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultNameStep.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultNotEqualsExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultNumberExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultOrExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultPathExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultPlusExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultPredicate.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultPredicated.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultProcessingInstructionNodeStep.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultRelationalExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultRelativeLocationPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultStep.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultTextNodeStep.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultTruthExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultUnaryExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultUnionExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultVariableReferenceExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultXPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\DefaultXPathFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\Expr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\FilterExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\FunctionCallExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableAncestorAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableAncestorOrSelfAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableAttributeAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableChildAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableDescendantAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableDescendantOrSelfAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableFollowingAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableFollowingSiblingAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableNamespaceAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableParentAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterablePrecedingAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterablePrecedingSiblingAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\iter\IterableSelfAxis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\LiteralExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\LocationPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\NumberExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\PathExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\Predicate.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\Predicated.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\PredicateSet.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\Step.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\UnaryExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\UnionExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\VariableReferenceExpr.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\XPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\expr\XPathFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\BooleanFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\CeilingFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\ConcatFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\ContainsFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\CountFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\DocumentFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\ext\EvaluateFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\ext\MatrixConcatFunction$MatrixEnum.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\ext\MatrixConcatFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\FalseFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\FloorFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\IdFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\LastFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\LocalNameFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\NameFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\NamespaceUriFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\NormalizeSpaceFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\NotFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\NumberFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\PositionFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\RoundFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\StartsWithFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\StringFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\StringLengthFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\SubstringAfterFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\SubstringBeforeFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\SubstringFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\SumFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\function\TrueFunction.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\Function.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\FunctionCallException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\FunctionContext.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\JaxenException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\JaxenHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\JaXPath.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\NamespaceContext.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\Navigator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\AnyChildNodeTest.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\AnyNodeTest.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\LocationPathPattern.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\NamespaceTest.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\NameTest.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\NodeTest.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\NodeTypeTest.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\NoNodeTest.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\Pattern.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\PatternHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\PatternParser.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\TextNodeTest.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\pattern\UnionPattern.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\QualifiedName.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\SimpleFunctionContext.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\SimpleNamespaceContext.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\SimpleVariableContext.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\UnresolvableException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\UnsupportedAxisException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\AncestorAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\AncestorOrSelfAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\DescendantAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\DescendantOrSelfAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\FollowingAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\FollowingSiblingAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\IdentityHashMap$1.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\IdentityHashMap$2.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\IdentityHashMap$3.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\IdentityHashMap$EmptyHashIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\IdentityHashMap$Entry.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\IdentityHashMap$HashIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\IdentityHashMap.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\LinkedIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\PrecedingAxisIterator$ReverseDescendantOrSelfAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\PrecedingAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\PrecedingSiblingAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\SelfAxisIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\SingleObjectIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\util\StackedIterator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\VariableContext.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\XPathFunctionContext$Singleton.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\XPathFunctionContext.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\jaxen\XPathSyntaxException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\Axis.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\conformance\ConformanceXPathHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\helpers\XPathReaderFactory.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\Operator.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\SAXPathEventSource.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\SAXPathException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\SAXPathParseException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\XPathHandler.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\XPathReader.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
CLASS: .\src\test\jars\dom4j-1.1\org\saxpath\XPathSyntaxException.class
minor[] = [0, 3]
major[] = [0, 45]
major = 'JDK 1.1 = 45'
=== SUMMARY ===
Different major version count: 2
'JDK 1.3 = 47' class count: 118
'JDK 1.1 = 45' class count: 333
=== DONE ===
</code></pre>
<h1 id="summary">Summary</h1>
<p>The ferris-magic-number project isn’t too complicated. It produces some interesting information, but, if you are having a problem with a dependency having a newer Java version than you support, this is when it really can be useful. That exact problem is what I needed to solve. Enjoy!</p>
<h1 id="references">References</h1>
<p>Java class file. (n.d.). Wikipedia. Retrieved from <a href="https://en.wikipedia.org/wiki/Java_class_file#Magic_Number">https://en.wikipedia.org/wiki/Java_class_file#Magic_Number</a>.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-33993365263654327362020-02-23T13:24:00.000-06:002020-02-23T13:54:43.579-06:00Testing Maven Release Plugin Auto-Increment Version Number<h1 id="abstract">Abstract</h1>
<p>The Maven release plugin is a very powerful tool and I rely on it heavily for coordinating software releases. Typically, software release version numbers follow a simple <code>1.0.0.0-SNAPSHOT</code> format. But recently I had need to add a qualifier to the version number - something like <code>1.0-beta-SNAPSHOT</code> or <code>1.0.0-fix-bug-description-SNAPSHOT</code>. After my 1st attempt to add the qualifier, the Maven release plugin auto-incremented the version number in an unexpected way. Therefore, I needed to research the Maven release plugin version auto-increment behavior but do so without making a bunch of nonsense tags in the production Git repository (I didn’t have a dev repository to work with). The purpose of this blog is to show how the Maven release plugin can be configured so it can run locally and not make changes to production source control.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>NetBeans 11.2</li>
<li>Maven 3.3.9 (Bundled with NetBeans)</li>
<li>maven-release-plugin 2.5.1</li>
<li>Java 11 (zulu11.35.15-ca-jdk11.0.5-win_x64)</li>
<li>Git for Windows 2.25.0</li>
</ul>
<blockquote>
<p><strong>NOTE</strong>
The Maven release plugin assumes executables (like git) are on the <code>PATH</code>. If you get an error that the <code>git</code> command is not found, you need to make sure Git is installed independent of your IDE and on the <code>PATH</code>.</p>
</blockquote>
<h1 id="download">Download</h1>
<p>Visit my GitHub page <a href="https://github.com/mjremijan">https://github.com/mjremijan</a> to see all of my open source projects. The code for this post is located at: <a href="https://github.com/mjremijan/thoth-maven-release-plugin" title="thoth-maven-release-plugin">https://github.com/mjremijan/thoth-maven-release-plugin</a></p>
<h1 id="installgit">Install Git</h1>
<p>The 1st thing you’ll need to do is install Git. Even if Git is integrated into your IDE (like with NetBeans), the Maven release plugin assumes the executables it needs are available on the PATH. Download, install, and configure PATH for your environment.</p>
<h1 id="pomltscmgttag">POM <scm> Tag</h1>
<p>The 2nd thing you’ll need to do is configure the POM <scm> tag. When the Maven release plugin runs, it uses the <scm> tag values to identify the location of the source control system.</p>
<p>The relevant configuration is below, see the <a href="https://github.com/mjremijan/thoth-maven-release-plugin/blob/master/pom.xml" title="full pom.xml">full pom.xml</a> on GitHub.</p>
<p>Because I want to run locally and not connect to a production source control system, my example uses Git. Git runs very nicely without needing a server. Let’s take a look at the <scm> tag.</p>
<h1 id="listing1-ltscmgttag">Listing 1 - <scm> tag</h1>
<pre><code class="xml"><scm>
<url>scm:git:file://D:/Projects/thoth-maven-release-plugin/.git</url>
<connection>
scm:git:file://D:/Projects/thoth-maven-release-plugin/.git</connection>
<developerConnection>
scm:git:file://D:/Projects/thoth-maven-release-plugin/.git
</developerConnection>
</scm>
</code></pre>
<p>The code in Listing 1 shows my configuration for the <scm> tag. Obviously, the reference to the project’s <code>.git</code> folder on the file system of my computer should stand out to you. <strong>You must change this value for your computer.</strong> Remember, the goal is to work locally! This configuration gets you there.</p>
<p>After configuring the <scm> tag, the next thing to do is configure the maven-release-plugin. Let’s look at that next.</p>
<h1 id="pommaven-relase-plugin">POM maven-relase-plugin</h1>
<p>The 3rd thing to do is configure the maven-release-plugin artifact. This configuration is for the Maven staging repository.</p>
<p>The relevant configuration is below, see the <a href="https://github.com/mjremijan/thoth-maven-release-plugin/blob/master/pom.xml" title="full pom.xml">full pom.xml</a> on GitHub. Let’s take a look at this configuration.</p>
<h1 id="listing2-maven-release-plugintag">Listing 2 - maven-release-plugin tag</h1>
<pre><code class="xml"><build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.1</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
<configuration>
<stagingRepository>
localforme::default::file://C:/Users/Michael/.m2/repository
</stagingRepository>
</configuration>
</plugin>
....
</build>
</code></pre>
<p>Line 6 sets the maven-release-plugin to version <code>2.5.1</code>. This was current at the time of my research (2019 Q2). Using a version below <code>2.5.2</code> has some additional gotchas which I’ll get to next.</p>
<p>Line 8 sets the maven-invoker dependency to version <code>2.2</code>. Maven 3.3 changed the script name (on Windows) from <code>mvn.bat</code> to <code>mvn.cmd</code>. The maven-release-plugin below version <code>2.5.2</code> assumes <code>mvn.bat</code>. Changing maven-invoker to version <code>2.2</code> changes the assumption to <code>mvn.cmd</code> so Maven 3.3+ can be used (Lucas, 2015). If you use maven-release-plugin greater than or equal to version <code>2.5.2</code>, you don’t need this maven-invoker configuration.</p>
<p>Line 15 sets the location of the Maven staging repository. Typically you would have this set in <code>settings.xml</code>. If it’s not set, you’ll get build errors. I show how to configure it here. If you have it in <code>settings.xml</code> you can remove this configuration.</p>
<p>Now that the <code>pom.xml</code> is configured, let’s look next at how to run this.</p>
<h1 id="mvncommand">MVN Command</h1>
<p>With the configuration now set, it’s time to run the Maven project and test that everything is working. Listing 3 shows the properties and switches for <code>mvn</code>.</p>
<h1 id="listing3-mvncommand">Listing 3 - mvn command</h1>
<pre><code class="bash">mvn
-Dmaven.test.skip=true
-Dmaven.javadoc.failOnError=false
--batch-mode release:clean release:prepare release:stage
</code></pre>
<p>There is not much to this command line. Line 4 is the most important. It specifies the maven-release-plugin goals to execute.</p>
<p>Now you are ready for testing. Run the command and see what happens.</p>
<h1 id="summary">Summary</h1>
<p>If you are using an IDE like NetBeans which automatically watches and reloads files when changes are detected on the file system, you can open the <code>pom.xml</code>, then run the <code>mvn</code> command, and finally watch the <version> tag automatically change as Maven is running. This way you can start with whatever value you want for the <version> tag and research how it automatically gets changed. Enjoy!</p>
<h1 id="references">References</h1>
<p>Lucas. (2015, July 1). <em>Failed to execute goal maven preprepared</em> [Web log comment]. Stackoverflow. Retrieved from <a href="https://stackoverflow.com/questions/29755620/failed-to-execute-goal-maven-releaseprepare">https://stackoverflow.com/questions/29755620/failed-to-execute-goal-maven-releaseprepare</a>.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-62155763303402159252020-01-21T15:31:00.001-06:002020-01-21T15:31:51.173-06:00Java Mail Sent Over TLS<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="utf-8"/>
<title>Blogger</title>
<meta name="author" content=""/>
</head>
<body>
<h1 id="abstract">Abstract</h1>
<p>The purpose of this blog is to demonstrate how to use Java Mail to send an email using an SMTP server with a TLS connection.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>NetBeans 11.2</li>
<li>Maven 3.3.9 (bundled with NetBeans)</li>
<li>Java 11 (zulu11.35.15-ca-jdk11.0.5-win_x64)</li>
</ul>
<pre><code class="xml"><dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
</code></pre>
<h1 id="download">Download</h1>
<p>Visit my GitHub page <a href="https://github.com/mjremijan">https://github.com/mjremijan</a> to see all of my open source projects. The code for this post is located at <a href="https://github.com/mjremijan/thoth-email" title="thoth-email">https://github.com/mjremijan/thoth-email</a> in the <a href="https://github.com/mjremijan/thoth-email/tree/master/thoth-email-via-tls" title="thoth-email-via-tls">thoth-email-via-tls</a> module.</p>
<h1 id="properties">Properties</h1>
<p>This example uses an <code>smtp-tls-outlook.properties</code> file to hold the SMTP server information. I used my personal Outlook account for testing, hence the use of the word <code>outlook</code> in the name of the properties file. What’s important are the contents of the file, shown in Listing 1.</p>
<h1 id="listing1-propertiesfile">Listing 1 - Properties file</h1>
<pre><code class="properties"># This is the name of the SMTP host machine.
host=
# This is the port number of the SMTP host machine.
# The same host may support both SSL and TLS but on
# different ports. So make sure you get the TLS port.
port=
# This is what you use in the “username” field when
# you login. Typically this is the same as your email
# address, but this isn’t always the case.
username=
# This is what you use in the “password” field when
# you login. This value is CLEAR TEXT, so keep this
# properties file safe.
password=
# This is the email address you want for the
# email’s FROM field. Enter the value using
# the format shown below. Typically this is
# just your email address for the account.
from=FIRSTNAME LASTNAME <ADDRESS@EMAIL.COM>
# This is the email address you want for the
# email’s REPLY_TO field. Enter the value using
# the format shown below. Typically this is
# just your email address for the account. Also
# typically this is the same as `from` above.
# But be warned, if an email’s FROM and REPLY_TO
# are different, that’s may be flagged as spam
# and never be delivered. So keep `from` and
# `reply` the same for initial testing
reply=FIRSTNAME LASTNAME <ADDRESS@EMAIL.COM>
# This is the email address you want to send
# the email to. For testing, it’s a good idea
# to send it to yourself first.
to=FIRSTNAME LASTNAME <ADDRESS@EMAIL.COM>
</code></pre>
<p>Now that you have a properties file, next let’s take a look at the code.</p>
<h1 id="code">Code</h1>
<p>This is a JUnit test demonstrating how to use Java Mail to send an email using an SMTP server with a TLS connection. Listing 2 shows the code.</p>
<blockquote>
<p><strong>NOTE</strong>
For initial testing, always check your SPAM folder. A rule can always be added to deliver to your INBOX.</p>
</blockquote>
<h1 id="listing2-javamailexample">Listing 2 - Java Mail example</h1>
<pre><code class="java">package org.thoth.email.via.tls;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class TlsTest {
public TlsTest() {
}
protected String now, hostname;
protected Properties outlook;
@BeforeEach
public void setUp() throws Exception {
now = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss a").format(new Date());
hostname = InetAddress.getLocalHost().getHostName();
outlook = new Properties();
outlook.load(this.getClass().getResourceAsStream("/smtp-tls-outlook.properties"));
}
@Test
public void a_test() throws Exception {
// Create MimeMultipart
MimeMultipart content = new MimeMultipart("related");
// html part
{
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText("<html><body>"
+ "<p>Time: "+now+"</p>"
+ "<p>From: "+hostname+"</p>"
+ "</body></html>"
, "UTF8", "html");
content.addBodyPart(textPart);
}
// properties
Properties props = new Properties();
{
props.setProperty("mail.smtp.auth", "true");
props.setProperty("mail.smtp.host", outlook.getProperty("host"));
props.setProperty("mail.smtp.port", outlook.getProperty("port"));
props.setProperty("mail.smtp.starttls.enable", "true");
}
Session smtp = null;
{
smtp = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
outlook.getProperty("username")
, outlook.getProperty("password")
);
}
});
smtp.setDebug(true);
smtp.setDebugOut(System.out);
}
MimeMessage m = new MimeMessage(smtp);
{
m.setRecipient(Message.RecipientType.TO, new InternetAddress(outlook.getProperty("to")));
m.setSubject("thoth-email TLS test " + now);
InternetAddress from = null;
{
from = new InternetAddress(outlook.getProperty("from"));
from.setPersonal("Thoth Email");
m.setFrom(from);
}
InternetAddress reply = null;
{
reply = new InternetAddress(outlook.getProperty("reply"));
m.setReplyTo(new InternetAddress[] {reply});
}
m.setContent(content);
}
Transport.send(m);
}
}
</code></pre>
<h1 id="summary">Summary</h1>
<p>The code for sending mail is not very hard. Successfully receiving an email without being flagged as SPAM is another matter. But if you follow this example, use a valid account, and don’t overuse it, you should be OK. This blog shows how to use Java Mail to send an email using an SMTP server with a TLS connection.</p>
</body>
</html>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-54305408080700300012020-01-21T15:09:00.001-06:002020-01-21T15:09:10.212-06:00Java Mail Sent Over SSL<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="utf-8"/>
<title>Blogger</title>
<meta name="author" content=""/>
</head>
<body>
<h1 id="abstract">Abstract</h1>
<p>The purpose of this blog is to demonstrate how to use Java Mail to send an email using an SMTP server with an SSL connection.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>NetBeans 11.2</li>
<li>Maven 3.3.9 (bundled with NetBeans)</li>
<li>Java 11 (zulu11.35.15-ca-jdk11.0.5-win_x64)</li>
</ul>
<pre><code class="xml"><dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
</code></pre>
<h1 id="download">Download</h1>
<p>Visit my GitHub page <a href="https://github.com/mjremijan">https://github.com/mjremijan</a> to see all of my open source projects. The code for this post is located at <a href="https://github.com/mjremijan/thoth-email" title="thoth-email">https://github.com/mjremijan/thoth-email</a> in the <a href="https://github.com/mjremijan/thoth-email/tree/master/thoth-email-via-ssl" title="thoth-email-via-ssl">https://github.com/mjremijan/thoth-email/tree/master/thoth-email-via-ssl</a> module.</p>
<h1 id="properties">Properties</h1>
<p>This example uses an <code>smtp-ssl-yahoo.properties</code> file to hold the SMTP server information. I used my personal Yahoo! account for testing, hence the use of the word <code>yahoo</code> in the name of the properties file. What’s important are the contents of the file, shown in Listing 1.</p>
<h1 id="listing1-propertiesfile">Listing 1 - Properties file</h1>
<pre><code class="properties"># This is the name of the SMTP host machine.
host=
# This is the port number of the SMTP host machine.
# The same host may support both SSL and TLS but on
# different ports. So make sure you get the SSL port.
port=
# This is what you use in the “username” field when
# you login. Typically this is the same as your email
# address, but this isn’t always the case.
username=
# This is what you use in the “password” field when
# you login. This value is CLEAR TEXT, so keep this
# properties file safe.
password=
# This is the email address you want for the
# email’s FROM field. Enter the value using
# the format shown below. Typically this is
# just your email address for the account.
from=FIRSTNAME LASTNAME <ADDRESS@EMAIL.COM>
# This is the email address you want for the
# email’s REPLY_TO field. Enter the value using
# the format shown below. Typically this is
# just your email address for the account. Also
# typically this is the same as `from` above.
# But be warned, if an email’s FROM and REPLY_TO
# are different, that’s may be flagged as spam
# and never be delivered. So keep `from` and
# `reply` the same for initial testing
reply=FIRSTNAME LASTNAME <ADDRESS@EMAIL.COM>
# This is the email address you want to send
# the email to. For testing, it’s a good idea
# to send it to yourself first.
to=FIRSTNAME LASTNAME <ADDRESS@EMAIL.COM>
</code></pre>
<p>Now that you have a properties file, next let’s take a look at the code.</p>
<h1 id="code">Code</h1>
<p>This is a JUnit test demonstrating how to use Java Mail to send an email using an SMTP server with an SSL connection. Listing 2 shows the code.</p>
<blockquote>
<p><strong>NOTE</strong>
For initial testing, always check your SPAM folder. A rule can always be added to deliver to your INBOX.</p>
</blockquote>
<h1 id="listing2-javamailexample">Listing 2 - Java Mail example</h1>
<pre><code class="java">package org.thoth.email.via.ssl;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class SslTest {
public SslTest() {
}
protected String now, hostname;
protected Properties yahoo;
@BeforeEach
public void setUp() throws Exception {
now = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss a").format(new Date());
hostname = InetAddress.getLocalHost().getHostName();
yahoo = new Properties();
yahoo.load(this.getClass().getResourceAsStream("/smtp-ssl-yahoo.properties"));
}
@Test
public void a_test() throws Exception {
// Create MimeMultipart
MimeMultipart content = new MimeMultipart("related");
// html part
{
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText("<html><body>"
+ "<p>Time: "+now+"</p>"
+ "<p>From: "+hostname+"</p>"
+ "</body></html>"
, "UTF8", "html");
content.addBodyPart(textPart);
}
// properties
Properties props = new Properties();
{
props.setProperty("mail.smtp.auth", "true");
props.setProperty("mail.smtp.host", yahoo.getProperty("host"));
props.setProperty("mail.smtp.socketFactory.port", yahoo.getProperty("port"));
props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
}
Session smtp = null;
{
smtp = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
yahoo.getProperty("username")
, yahoo.getProperty("password")
);
}
});
smtp.setDebug(true);
smtp.setDebugOut(System.out);
}
MimeMessage m = new MimeMessage(smtp);
{
m.setRecipient(Message.RecipientType.TO, new InternetAddress(yahoo.getProperty("to")));
m.setSubject("thoth-email SSL test " + now);
InternetAddress from = null;
{
from = new InternetAddress(yahoo.getProperty("from"));
from.setPersonal("Thoth Email");
m.setFrom(from);
}
InternetAddress reply = null;
{
reply = new InternetAddress(yahoo.getProperty("reply"));
m.setReplyTo(new InternetAddress[] {reply});
}
m.setContent(content);
}
Transport.send(m);
}
}
</code></pre>
<h1 id="summary">Summary</h1>
<p>The code for sending mail is not very hard. Successfully receiving an email without being flagged as SPAM is another matter. But if you follow this example, use a valid account, and don’t overuse it, you should be OK. This blog shows how to use Java Mail to send an email using an SMTP server with an SSL connection.</p>
</body>
</html>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-75824537601446164942018-11-27T07:54:00.004-06:002018-11-27T07:54:32.624-06:00Postfix relay through Outlook (TLS)<h1 id="abstract">Abstract</h1>
<p>We take email for granted. It seems so easy. Just click the send button. But spam has made sending and receiving email a lot more complicated. This is especially true for people who like to run their own networks at home. If you are like me, you might have 1 or more physical machines running multiple virtual machines, not to mention multiple containers as well. All this network infrastructure needs to communicate with you somehow. After all, how do you keep tabs on whether or not your CRON jobs are successful? Email is the way. But sending email out from a home network without it being blocked at various points along the way can get complicated. We can go through the cost of buying a domain and email service, but who wants the cost? So how do we get the email through? This post explains how to configure Postfix to relay email through an Outlook account over TLS. </p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>An Outlook account (outlook.com, live.com, etc.)</li>
<li>Ubuntu 18.04.1 LTS</li>
<li>Postfix 3.3.0</li>
</ul>
<h1 id="installpostfix">Install Postfix</h1>
<p>I’m not going to go into a lot of explanation. I’m simply going to state the steps and configuration I did which got everything working.</p>
<p>First thing you need to do is install Postfix.</p>
<pre><code class="bash">$ sudo apt-get update
$ sudo apt-get install postfix mailutils
</code></pre>
<blockquote>
<p><strong>NOTE</strong>
For the Postfix installation, when it asks for the “System mail name” value, it should be the same as the name of the server.</p>
</blockquote>
<h1 id="configurepostfixauthenticationforoutlook">Configure Postfix Authentication for Outlook</h1>
<p>Configure Postfix to login to Outlook’s SMTP server. This configures the server name, port, account id, and clear text password.</p>
<pre><code class="bash"># Create the password file
$ cd /etc/postfix/sasl
$ touch sasl_passwd_outlook
$ chmod 600 sasl_passwd_outlook
</code></pre>
<p>Now edit the <code>sasl_passwd_outlook</code> file using your favorite editor. Make it look like Listing 1, replacing <em>ACCOUNT_NAME</em> and <em>CLEAR_TEXT_PASSWD</em> appropriately.</p>
<h1 id="listing1-sasl_passwd_outlook">Listing 1 - sasl_passwd_outlook</h1>
<pre><code>[smtp-mail.outlook.com]:587 ACCOUNT_NAME@outlook.com:CLEAR_TEXT_PASSWD
</code></pre>
<p>Now hash the <code>sasl_passwd_outlook</code> file into a Postfix .db file</p>
<pre><code class="bash">$ postmap sasl_passwd_outlook
</code></pre>
<h1 id="configurepostfixemailmappingforoutlook">Configure Postfix Email Mapping for Outlook</h1>
<p>If user <code>mike</code> attempts to send an email from a server with the name <code>bluegreensky</code>, the default Postfix <strong>FROM</strong> address will be <code>mike@bluegreensky</code>. This is not good because if you try to relay this email through Outlook, it will be blocked since the <strong>FROM</strong> address does not match the Outlook account. To get around this, configure a regular expression mapping file that will change all local email addresses (like <code>mike@bluegreensky</code>) to the Outlook account email address.</p>
<p>First, create a generic map file that’s empty</p>
<pre><code class="bash">$ mkdir /etc/postfix/map
$ cd /etc/postfix/map
$ touch generic_map
$ chmod 600 generic_map
# Hash the file into a *.db file
$ postmap /etc/postfix/map/generic_map
</code></pre>
<p>Next, create an Outlook map file that will change local email address to the Outlook account email address.</p>
<pre><code class="bash">$ mkdir /etc/postfix/map
$ cd /etc/postfix/map
$ touch regex_map_outlook
$ chmod 600 regex_map_outlook
</code></pre>
<p>Now edit the <code>regex_map_outlook</code> file using your favorite editor. Make it look like Listing 2, replacing <em>HOSTNAME</em> - just <em>HOSTNAME</em>, <strong>not</strong> <em>@HOSTNAME</em>…don’t lose the @ character - and <em>ACCOUNT_NAME</em> appropriately.</p>
<h1 id="listing2-regex_map_outlook">Listing 2 - regex_map_outlook</h1>
<pre><code>/.+@HOSTNAME/ ACCOUNT_NAME@outlook.com
</code></pre>
<p>Now hash the <code>regex_map_outlook</code> file into a Postfix .db file</p>
<pre><code class="bash">$ postmap regex_map_outlook
</code></pre>
<h1 id="configurepostfixtlsforoutlook">Configure Postfix TLS for Outlook</h1>
<p>TLS must be used to connect to Outlook SMTP servers. During Postfix installation, a <code>main.cf</code> is created. It must be edited</p>
<pre><code class="bash">$ cd /etc/postfix
</code></pre>
<p>Now edit the <code>main.cf</code> file using your favorite editor. In Listing 3 are the values you must add or update in <code>main.cf</code>. For each of the name/value pairs below, search <code>main.cf</code> to see if it already exists. If so, use my value below. If not, add my value to the end of <code>main.cf</code>.</p>
<h1 id="listing3-main.cf">Listing 3 - main.cf</h1>
<pre><code># Outlook
relayhost = [smtp-mail.outlook.com]:587
smtp_use_tls = yes
smtp_sasl_auth_enable = yes
smtp_sasl_security_options =
smtp_sasl_password_maps = hash:/etc/postfix/sasl/sasl_passwd_outlook
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_protocols = !SSLv2,!SSLv3,!TLSv1.1
smtp_tls_ciphers = medium
smtp_tls_mandatory_ciphers = medium
smtp_tls_security_level = may
smtp_generic_maps = hash:/etc/postfix/map/generic_map, regexp:/etc/postfix/map/regex_map_outlook
</code></pre>
<h1 id="restartpostfix">Restart Postfix</h1>
<p>Restart Postfix so it picks up all the new configuration.</p>
<p><code>bash
$ service postfix restart
</code> </p>
<h1 id="testingpostfix">Testing Postfix</h1>
<p>If all goes well, Postfix restarted without any errors and is now configured to relay email from the server through the Outlook account. I typically test this with both <code>mail</code> and <code>at</code>.</p>
<pre><code class="bash"># Test sending mail directly
$ echo "test email message" | mail -s "test email from server" some_email_address@someprovider.com
</code></pre>
<pre><code class="bash"># Test sending mail through scheduler at
$ echo "echo \"gosh golly, it is AT\"" | at now
</code></pre>
<p>Do this testing, and see if you get the email your are expecting.</p>
<blockquote>
<p><strong>NOTE</strong>
When testing with <code>at</code>, you will want to create a <code>~/.forward</code> file with your email address so the results of the scheduled job are not delivered to your server’s local account’s inbox. </p>
</blockquote>
<h1 id="summary">Summary</h1>
<p>This post shows how to configure Postfix to relay email through an Outlook account over TLS. Sending email from a home network and have it not be blocked is tough. This configuration got me working. I hope it works for you.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com1tag:blogger.com,1999:blog-383035171837618940.post-3940463014330107302018-11-26T15:38:00.000-06:002018-11-27T07:53:55.278-06:00Postfix relay through Yahoo! (SSL)<h1 id="abstract">Abstract</h1>
<p>We take email for granted. It seems so easy. Just click the send button. But spam has made sending and receiving email a lot more complicated. This is especially true for people who like to run their own networks at home. If you are like me, you might have 1 or more physical machines running multiple virtual machines, not to mention multiple containers as well. All this network infrastructure needs to communicate with you somehow. After all, how do you keep tabs on whether or not your CRON jobs are successful? Email is the way. But sending email out from a home network without it being blocked at various points along the way can get complicated. We can go through the cost of buying a domain and email service, but who wants the cost? So how do we get the email through? This post explains how to configure Postfix to relay email through a Yahoo! account over SSL. </p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>A Yahoo! account</li>
<li>Ubuntu 18.04.1 LTS</li>
<li>Postfix 3.3.0</li>
</ul>
<h1 id="installpostfix">Install Postfix</h1>
<p>I’m not going to go into a lot of explanation. I’m simply going to state the steps and configuration I did which got everything working.</p>
<p>First thing you need to do is install Postfix.</p>
<pre><code class="bash">$ sudo apt-get update
$ sudo apt-get install postfix mailutils
</code></pre>
<blockquote>
<p><strong>NOTE</strong>
For the Postfix installation, when it asks for the “System mail name” value, it should be the same as the name of the server.</p>
</blockquote>
<h1 id="configureyahooaccount">Configure Yahoo! Account</h1>
<p>The Yahoo! Account’s security setting must be set to allow password authentication. By default, it is not enabled. Go to the Yahoo! Account Security page and make sure “Password is enabled” is set as showing in Figure 1.</p>
<h1 id="figure1-yahooaccountsecurity">Figure 1 - Yahoo! Account Security</h1>
<figure>
<img src="https://rkyoug.dm.files.1drv.com/y4mW7s3EGk-pBWgX87xiYe5sC7Aby6E_sL3jXe5c6n2If61avumOqdV45S-Vu1m30kQjRy1NVL_lm1M9Ib4a0HAs74bhgVNBEW4jLp8J7TGGx7pQVM0837unZf_Gao-7vKjb5TVP66cTGpi4oxAKGtYLYz86siwwHmLHiis0nAdAUPa3vvXgGueZTjrr3yFVlqRUKGdl_jmdFJ0BT00hEaEtQ?width=1346&height=866&cropmode=none" alt="Yahoo! Account Security" title="Yahoo! Account Security" />
<figcaption>Yahoo! Account Security</figcaption></figure>
<h1 id="configurepostfixauthenticationforyahoo">Configure Postfix Authentication for Yahoo!</h1>
<p>Configure Postfix to login to Yahoo!’s SMTP server. This configures the server name, port, account id, and clear text password.</p>
<pre><code class="bash"># Create the password file
$ cd /etc/postfix/sasl
$ touch sasl_passwd_yahoo
$ chmod 600 sasl_passwd_yahoo
</code></pre>
<p>Now edit the <code>sasl_passwd_yahoo</code> file using your favorite editor. Make it look like Listing 1, replacing <em>ACCOUNT_NAME</em> and <em>CLEAR_TEXT_PASSWD</em> appropriately.</p>
<h1 id="listing1-sasl_passwd_yahoo">Listing 1 - sasl_passwd_yahoo</h1>
<pre><code>[smtp.mail.yahoo.com]:465 ACCOUNT_NAME@yahoo.com:CLEAR_TEXT_PASSWD
</code></pre>
<p>Now hash the <code>sasl_passwd_yahoo</code> file into a Postfix .db file</p>
<pre><code class="bash">$ postmap sasl_passwd_yahoo
</code></pre>
<h1 id="configurepostfixemailmappingforyahoo">Configure Postfix Email Mapping for Yahoo!</h1>
<p>If user <code>mike</code> attempts to send an email from a server with the name <code>bluegreensky</code>, the default Postfix <strong>FROM</strong> address will be <code>mike@bluegreensky</code>. This is not good because if you try to relay this email through Yahoo!, it will be blocked since the <strong>FROM</strong> address does not match the Yahoo! account. To get around this, configure a regular expression mapping file that will change all local email addresses (like <code>mike@bluegreensky</code>) to the Yahoo! account email address.</p>
<p>First, create a generic map file that’s empty</p>
<pre><code class="bash">$ mkdir /etc/postfix/map
$ cd /etc/postfix/map
$ touch generic_map
$ chmod 600 generic_map
# Hash the file into a *.db file
$ postmap /etc/postfix/map/generic_map
</code></pre>
<p>Next, create a yahoo map file that will change local email address to the Yahoo! account email address.</p>
<pre><code class="bash">$ mkdir /etc/postfix/map
$ cd /etc/postfix/map
$ touch regex_map_yahoo
$ chmod 600 regex_map_yahoo
</code></pre>
<p>Now edit the <code>regex_map_yahoo</code> file using your favorite editor. Make it look like Listing 2, replacing <em>HOSTNAME</em> - just <em>HOSTNAME</em>, <strong>not</strong> <em>@HOSTNAME</em>…don’t lose the @ character - and <em>ACCOUNT_NAME</em> appropriately.</p>
<h1 id="listing2-regex_map_yahoo">Listing 2 - regex_map_yahoo</h1>
<pre><code>/.+@HOSTNAME/ ACCOUNT_NAME@yahoo.com
</code></pre>
<p>Now hash the <code>regex_map_yahoo</code> file into a Postfix .db file</p>
<pre><code class="bash">$ postmap regex_map_yahoo
</code></pre>
<h1 id="configurepostfixsslforyahoo">Configure Postfix SSL for Yahoo!</h1>
<p>SSL must be used to connect to Yahoo! SMTP servers. During Postfix installation, a <code>main.cf</code> is created. It must be edited</p>
<pre><code class="bash">$ cd /etc/postfix
</code></pre>
<p>Now edit the <code>main.cf</code> file using your favorite editor. In Listing 3 are the values you must add or update in <code>main.cf</code>. For each of the name/value pairs below, search <code>main.cf</code> to see if it already exists. If so, use my value below. If not, add my value to the end of <code>main.cf</code>.</p>
<h1 id="listing3-main.cf">Listing 3 - main.cf</h1>
<pre><code># Yahoo!
relayhost = [smtp.mail.yahoo.com]:465
smtp_use_tls = yes
smtp_sasl_auth_enable = yes
smtp_sasl_security_options =
smtp_sasl_password_maps = hash:/etc/postfix/sasl/sasl_passwd_yahoo
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_wrappermode = yes
smtp_tls_security_level = encrypt
smtp_generic_maps = hash:/etc/postfix/map/generic_map, regexp:/etc/postfix/map/regex_map_yahoo
</code></pre>
<h1 id="restartpostfix">Restart Postfix</h1>
<p>Restart Postfix so it picks up all the new configuration.</p>
<p><code>bash
$ service postfix restart
</code> </p>
<h1 id="testingpostfix">Testing Postfix</h1>
<p>If all goes well, Postfix restarted without any errors and is now configured to relay email from the server through the Yahoo! account. I typically test this with both <code>mail</code> and <code>at</code>.</p>
<pre><code class="bash"># Test sending mail directly
$ echo "test email message" | mail -s "test email from server" some_email_address@someprovider.com
</code></pre>
<pre><code class="bash"># Test sending mail through scheduler at
$ echo "echo \"gosh golly, it is AT\"" | at now
</code></pre>
<p>Do this testing, and see if you get the email your are expecting.</p>
<blockquote>
<p><strong>NOTE</strong>
When testing with <code>at</code>, you will want to create a <code>~/.forward</code> file with your email address so the results of the scheduled job are not delivered to your server’s local account’s inbox.</p>
</blockquote>
<h1 id="summary">Summary</h1>
<p>This post shows how to configure Postfix to relay email through a Yahoo! account over SSL. Sending email from a home network and have it not be blocked is tough. This configuration got me working. I hope it works for you.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com2tag:blogger.com,1999:blog-383035171837618940.post-2436435207290755012018-11-09T12:11:00.001-06:002018-11-09T12:11:55.229-06:00Derby Database Backup<h1 id="abstract">Abstract</h1>
<p>I have already posted a number of blogs about Derby:</p>
<ul>
<li><a href="https://mjremijan.blogspot.com/2018/08/multiple-derby-network-servers-on-same.html" title="Multiple Derby Network Servers on the same Host">Multiple Derby Network Servers on the same Host</a></li>
<li><a href="https://mjremijan.blogspot.com/2018/05/apache-derby-database-users-and.html" title="Apache Derby Database Users and Permissions">Apache Derby Database Users and Permissions</a></li>
<li><a href="https://mjremijan.blogspot.com/2014/03/integration-testing-with-maven-and-in.html" title="Integration Testing with Maven and an In-Memory Derby Database">Integration Testing with Maven and an In-Memory Derby Database</a></li>
</ul>
<p>This wasn’t intended to be a series. But over the years I’ve been using Derby more and more. Recently, I started using Derby as my database of choice for my Microservice architecture. These are personal-use applications, so Derby is more than sufficient. Even though these are personal-use applications, I require <a href="https://mjremijan.blogspot.com/2018/08/multiple-derby-network-servers-on-same.html" title="Multiple Derby Network Servers on the same Host">multiple servers</a> with <a href="https://mjremijan.blogspot.com/2018/05/apache-derby-database-users-and.html" title="Apache Derby Database Users and Permissions">limited user permissions</a> and - most importantly - backup. I’d hate to lose my data! The purpose of this post is to demonstrate how to backup a Derby database.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li><a href="https://db.apache.org/derby/derby_downloads.html">Apache Derby</a> 10.14.2.0</li>
<li>OpenJDK 64-Bit Server VM <a href="https://www.azul.com/downloads/zulu/">Zulu</a>11.1+23 (build 11-ea+22, mixed mode)</li>
</ul>
<h1 id="download">Download</h1>
<p>There are no downloads with this blog post. The scripts are shown in full.</p>
<h1 id="derbysystemutility">Derby System Utility</h1>
<p>Backing up a Derby database is really quite simple. Derby has a built-in system utility for performing the backup. The utility is <code>SYSCS_UTIL.SYSCS_BACKUP_DATABASE('/location/of/the/backup/')</code>. When called, Derby will lock the database and perform the copy operation to the file system location you specify as the parameter to <code>SYSCS_BACKUP_DATABASE</code>. Now that we know the system utility to do the backup, let’s look at a bash script to automate it. </p>
<h1 id="backupscript">Backup Script</h1>
<p>Listing 1 is a bash script which can be easily modified to backup any Derby database on any network server.</p>
<h1 id="listing1-derby-mydatabase-backup.sh">Listing 1 - derby-mydatabase-backup.sh</h1>
<pre><code class="bash">#!/bin/bash
# Define a bunch of variables which will be used within this script.
# The names of the variables should be self-explanatory.
DERBY_HOME=/opt/db-derby-10.14.2.0-bin/
NETWORK_SERVER_HOST=localhost
NETWORK_SERVER_PORT=1527
DATABASE_NAME=mydatabase
DATABASE_USER=sa
DATABASE_PASSWORD=abc123
JDBC_URL="jdbc:derby://$NETWORK_SERVER_HOST:$NETWORK_SERVER_PORT/$DATABASE_NAME"
BACKUP_DIRECTORY="/tmp/$DATABASE_NAME-backup/$NETWORK_SERVER_PORT"
BACKUP_SCRIPT="$BACKUP_DIRECTORY/backup.sql"
# Remove old backup if it exists. It is not a good idea to
# perform a backup on top of an existing backup.
rm -rf $BACKUP_DIRECTORY
mkdir -p $BACKUP_DIRECTORY
cd $BACKUP_DIRECTORY
# Use the `echo` command to dynamically create an SQL file.
# This SQL file will be used by Derby `ij` to connect to
# the database and perform the backup.
echo "connect '$JDBC_URL' user '$DATABASE_USER' password '$DATABASE_PASSWORD';" >> $BACKUP_SCRIPT
echo "CALL SYSCS_UTIL.SYSCS_BACKUP_DATABASE('$BACKUP_DIRECTORY');" >> $BACKUP_SCRIPT
echo "exit;" >> $BACKUP_SCRIPT
# Run the Derby `ij` application, passing it the SQL file
# that was just dynamically created. `ij` will read the
# SQL file, executing its commands. This will then
# cause `ij` to connect to the database and call the
# system utility to perform the backup.
$DERBY_HOME/bin/ij $BACKUP_SCRIPT
</code></pre>
<p>Let’s take a look at this script in more detail.</p>
<p>Lines 5–15 setup a number of variables used within the script. Some variables are used to set the values of other variables. There is nothing too complicated here. The names of the variables are self-explanatory.</p>
<p>Lines 17–19 is file system maintenance. It is not a good idea to perform a backup on top of an existing backup. So these lines remove an existing backup (if it exists) and creates a new, empty, backup directory.</p>
<p>Lines 24–26 are then responsible for creating the <code>backup.sql</code> script file. This script file contains the SQL commands to perform the backup. Line 24 is the <code>connect</code> command so Derby <code>ij</code> can connect to the database you want to backup. Line 25 is where the magic happens with a call to the <code>SYSCS_BACKUP_DATABASE</code> system utility. The location of the backup directory is passed as a parameter to the utility. When this SQL command is executed, Derby will lock the database and perform the backup. Line 26 is the <code>exit</code> command to exit <code>ij</code>.</p>
<p>Line 33 is then finally where everything happens. The Derby <code>ij</code> command is called with the location of the dynamically created <code>backup.sql</code> file passed to <code>ij</code> as a command-line parameter. When bash executes line 33, and if everything goes well, the Derby database will be backed up.</p>
<blockquote>
<p><strong>NOTE</strong>
If you are running the Derby network server with a Java security policy, you may run into some problems with this script. the Java SecurityManager may prevent the network connection to the database or the SecurityManager my encounter permission problems writing to the backup directory.</p>
</blockquote>
<h1 id="summary">Summary</h1>
<p>Backing up a Derby database is pretty easy. Just call <code>SYSCS_UTIL.SYSCS_BACKUP_DATABASE('/location/of/the/backup/')</code>.</p>
<h1 id="references">References</h1>
<p>Backing Up a Database. (2013, January 24). db.apache.org. Retrieved from <a href="https://db.apache.org/derby/docs/10.0/manuals/admin/hubprnt43.html">https://db.apache.org/derby/docs/10.0/manuals/admin/hubprnt43.html</a>.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-70386370263110376882018-08-23T10:40:00.000-05:002018-08-23T10:40:18.005-05:00Multiple Derby Network Servers on the same Host<h1 id="abstract">Abstract</h1>
<p>Suppose you want to start multiple Derby network servers on the same host. They need to be listening on different ports and ideally store their data in different locations. The listings below show Linux bash and Windows batch scripts to configure starting a Derby network server. In this example, the Derby network server will listen on port <code>1110</code>. Each Derby network server will also have its own file system location to store its databases. </p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>Apache Derby 10.14.1.0</li>
<li>Java zulu11.1+23-ea-jdk11</li>
</ul>
<p>I am not going to go through the process of downloading and installing these technologies. I’ll leave that as an exercise for you.</p>
<h1 id="linuxbashscripts">Linux bash scripts</h1>
<p>Here are the Linux bash scripts to configure starting a Derby network server. We’ll look at the details of each file. First we’ll set the environment.</p>
<h1 id="listing1-setenv.sh">Listing 1 - setenv.sh</h1>
<pre><code class="bash">#!/bin/bash
export DERBY_HOME=/home/derby/opt/derby
export PATH="$DERBY_HOME/bin:$PATH"
echo "DERBY_HOME=$DERBY_HOME"
export JAVA_HOME=/home/derby/opt/java
echo "JAVA_HOME=$JAVA_HOME"
export NS_HOME=/var/local/derby/1110
mkdir -p $NS_HOME
echo "NS_HOME=$NS_HOME"
export NS_PORT=1110
echo "NS_PORT=$NS_PORT"
export NS_HOST=0.0.0.0
echo "NS_HOST=$NS_HOST"
export DERBY_OPTS=""
export DERBY_OPTS="$DERBY_OPTS -Dderby.drda.host=$NS_HOST"
export DERBY_OPTS="$DERBY_OPTS -Dderby.drda.portNumber=$NS_PORT"
export DERBY_OPTS="$DERBY_OPTS -Dderby.system.home=$NS_HOME"
</code></pre>
<p><strong>DERBY_HOME</strong> is self explanatory: it’s where Derby is unzipped (installed). Add Derby’s <code>bin</code> directory to the <code>PATH</code>.</p>
<p><strong>JAVA_HOME</strong> is self explanatory: it’s where Java is unzipped (installed). Add Java’s <code>bin</code> directory to the <code>PATH</code>.</p>
<p><strong>NS_HOME</strong> is “<strong>N</strong>etwork <strong>S</strong>erver Home”. This is the directory the Derby network server will use to store its configuration and databases. Whenever a new database is created on this Derby network server, a new sub-directory will be created under <code>NS_HOME</code> for the new database. This allows multiple Derby network servers running on the same host to keep their data separate.</p>
<p><strong>NS_PORT</strong> is self explanatory: it’s the port the Derby network server uses to listen for connections. This allows multiple Derby network servers to run on the same host.</p>
<p><strong>NS_HOST</strong> sets the network interface used by the Derby network server when listening for connections. By default, the Derby network server only listens for connections on the loopback address of <code>127.0.0.1</code>. This default means clients must run on the same host as the network server - not very useful. By setting the host to <code>0.0.0.0</code>, the Derby network server will listen for connections on any network interface on the host. If your Derby network server host has multiple network interfaces, <code>NS_HOST</code> should be set to the IP of one of those interfaces. Setting this value allows clients to be remote and run on any host. </p>
<p><strong>DERBY_OPTS</strong> is the system property used to get all of the configuration options to Derby. It’s value is created by concatenating together the appropriate Derby system properties with their associated values.</p>
<p>Now that the environment is set, we can start the server.</p>
<h1 id="listing2-start.sh">Listing 2 - start.sh</h1>
<pre><code class="bash">#!/bin/bash
# Directory of the script
SD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
# Source in common variables
source $SD/setenv.sh
startNetworkServer -noSecurityManager
</code></pre>
<p><strong>SD</strong> is <strong>S</strong>cript <strong>D</strong>irectory. The evaluation determines the fully-qualified file system location of the <code>start.sh</code> script and assigns it to <code>SD</code>. This is useful when referencing other scripts.</p>
<p><strong>source</strong> self explanatory: it sources in the environment configuration to run the Derby network server.</p>
<p><strong>startNetworkServer -noSecurityManager</strong> starts the Derby network server. The <code>DERBY_OPTS</code> variable - set in <code>setenv.sh</code> - is used to configure the network server. The <code>-noSecurityManager</code> command line option needs some explanation. By default, Derby’s Java process runs with a limited security policy. I’ve found that this limited security policy gets in the way of database operations which I normally would just expect to work. By specifying <code>-noSecurityManager</code>, you run the Derby network server without <strong>any</strong> security policy. This may not be the ideal way for you to run a Derby network server. However, this blog is limited in scope to running the server. Search for my other blog on how to secure a Derby network server.</p>
<p>Now that the server is running, we need to stop it.</p>
<h1 id="listing3-stop.sh">Listing 3 - stop.sh</h1>
<pre><code class="bash">#!/bin/bash
# Directory of the script
SD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
# Source in common variables
source $SD/setenv.sh
stopNetworkServer
</code></pre>
<p>All of this is self explanatory. No further comments are needed for this script.</p>
<h1 id="windowsbatchscripts">Windows batch scripts</h1>
<p>Here are the Windows batch scripts to configure starting a Derby network server. We will <strong>not</strong> look at these files in additional details; details are included with the Linux bash scripts.</p>
<h1 id="listing1-setenv.cmd">Listing 1 - setenv.cmd</h1>
<pre><code class="bash">@echo off
set DERBY_HOME=C:\Applications\Derby
set PATH=%DERBY_HOME%\bin;%PATH%
echo DERBY_HOME=%DERBY_HOME%
set JAVA_HOME=C:\Applications\Java
echo JAVA_HOME=%JAVA_HOME%
set NS_HOME=C:\Users\Derby\1110
md %NS_HOME% 2>NUL
echo NS_HOME=%NS_HOME%
set NS_PORT=1110
echo NS_PORT=%NS_PORT%
set NS_HOST=0.0.0.0
echo NS_HOST=%NS_HOST%
set DERBY_OPTS=-Dderby.drda.host=%NS_HOST%
set DERBY_OPTS=%DERBY_OPTS% -Dderby.drda.portNumber=%NS_PORT%
set DERBY_OPTS=%DERBY_OPTS% -Dderby.system.home=%NS_HOME%
</code></pre>
<h1 id="listing2-start.cmd">Listing 2 - start.cmd</h1>
<pre><code class="bash">@echo off
call setenv.cmd
startNetworkServer -noSecurityManager
</code></pre>
<h1 id="listing3-stop.cmd">Listing 3 - stop.cmd</h1>
<pre><code class="bash">@echo off
call setenv.cmd
stopNetworkServer
</code></pre>
<h1 id="summary">Summary</h1>
<p>That’s it. I hope you enjoyed learning how to run multiple Derby network servers on the same host.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-27351076402737400672018-05-23T18:48:00.000-05:002018-08-22T12:07:38.236-05:00Apache Derby Database Users and Permissions<h1 id="abstract">Abstract</h1>
<p>Apache Derby is awesome! Especially in a Microservices environment where the data for services (may) shrink and not require a heartier RDBMS. Derby is awesome because it’s so easy to use, especially when it come to users and permissions - you don’t need any! But, it may be the case you want to create an application-level user with limited permissions to use in Derby. The purpose of this blog is to document how to create application-level, limited permission users in Derby. </p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>Apache Derby 10.14.1.0</li>
<li>Java 1.8.0_152_x64</li>
</ul>
<p>I am not going to go through the process of downloading and installing these technologies. I’ll leave that as an exercise for you.</p>
<h1 id="runderbynetworkserver">Run Derby Network Server</h1>
<p>The first thing you must do is run a Derby network server. In my previous blog post titled <a href="https://mjremijan.blogspot.com/2016/06/multiple-derby-network-servers-on-same.html">Multiple Derby Network Servers on the same Host</a>, I give detailed instructions on how to do this. Please refer to that blog post to run your own Derby network server.</p>
<h1 id="configurederbynetworkserver">Configure Derby Network Server</h1>
<p>To configure the Derby network server, you need to create a <code>derby.properties</code> file. But where does the file go? It can go in a couple different places. Let’s take a look.</p>
<p>I’ll first assume that you ignored the <strong>Run Derby Network Server</strong> section above and instead are running Derby with all its defaults. If that’s the case, you probably started the network server by finding the <code>%DERBY_HOME%\bin\startNetworkServer.bat</code> file and double-clicking it. If you did this - highly not recommended - then Derby thinks the <code>%DERBY_HOME%\bin</code> directory is its system directory. You can confirm this by looking for the <code>%DERBY_HOME%\bin\derby.log</code> file. If confirmed, then you need to create a <code>%DERBY_HOME%\bin\derby.properties</code> file. Wherever the <code>derby.log</code> file is, that’s where you create the <code>derby.properties</code> file.</p>
<p>On the other hand if you didn’t ignore the <strong>Run Derby Network Server</strong> section above, congratulations! The <code>derby.properties</code> file must go into the directory set by the <code>-Dderby.system.home</code> Java system property.</p>
<p>Now that you know where to put the <code>derby.properties</code> file, here is (an example) of what to put in it:</p>
<pre><code class="bash"># Passwords don't expire for 10 years
derby.authentication.native.passwordLifetimeMillis=315360000000
# Use the best hash algorithm you can
derby.authentication.builtin.algorithm=SHA-512
# Use a larger salt length for better security
derby.authentication.builtin.saltLength=128
# Re-hash this number of times for better security
derby.authentication.builtin.iterations=1564
</code></pre>
<p>The first property <code>derby.authentication.native.passwordLifetimeMillis</code> is the most important one. It configures how long Derby user passwords can be used before they go stale. By default, a password goes stale in 31 days unless this property is configured.</p>
<blockquote>
<p><strong>NOTE</strong>
Derby uses the word “stale”, whereas we would typically use the word “expired”.</p>
</blockquote>
<p>The value of this property is in milliseconds. The example above - 315360000000 - means a user’s password goes stale 10 years <strong>after the user is created</strong>. This configuration is for <strong>all</strong> users in <strong>every</strong> database in that Derby network server. There does not seem to be any per-user password expire setting. </p>
<p>Now you have the <code>derby.properties</code> file in place. Start the network server and let’s use it.</p>
<h1 id="runij">Run ij</h1>
<p><code>ij</code> is to Derby what <code>sqlplus</code> is to Oracle; just a simple command-line interface to execute SQL. Find and run <code>%DERBY_HOME%\bin\ij.bat</code>. For the rest of the blog, the <code>"ij>"</code> prompt will indicate SQL commands that must be executed within <code>ij</code>. </p>
<p>With <code>ij</code> running, we can create a database. Let’s do that next.</p>
<h1 id="createadatabase">Create a Database</h1>
<p>First, make sure your Derby network server is configured (as described above), and running (as described above) and is waiting for connections on whatever port you configured. </p>
<p>Second, make sure <code>ij</code> is running. You should be at this prompt:</p>
<pre><code class="bash">ij>
</code></pre>
<p>Third, issue a connect statement which will create a new database.</p>
<pre><code class="bash">ij> connect 'jdbc:derby://localhost:11528/resiste;create=true;' user 'sa_resiste';
</code></pre>
<p>Let’s look at this in more detail. <code>localhost</code> assumes the Derby network server is running on the same machine as <code>ij</code>, though it can be any host on your network. <code>11528</code> is the port the Derby network server is using to listen for connections. <code>resiste</code> is the name of the database to connect to; remember a Derby network server can have many different databases. <code>create=true</code> tells Derby to create the database if it doesn’t exist already. When created, the <code>resiste</code> database will the file system folder <code>%derby.system.home%\resiste</code>. <code>sa_resiste</code> is name of the admin user for the <code>resiste</code> database. Usually it’s just <code>sa</code>. Next we’ll look at setting the password for this admin user.</p>
<h1 id="createtheadminuser">Create the Admin User</h1>
<p>Derby provides an internal system call to create users and passwords. Make sure you are connected to the <code>resiste</code> database then execute: </p>
<pre><code class="bash">ij> CALL SYSCS_UTIL.SYSCS_CREATE_USER('sa_resiste', 'derby123');
ij> disconnect;
ij> exit;
</code></pre>
<p>Line 1 creates the <code>sa_resiste</code> user with the password <code>derby123</code>. Lines 3 and 4 then disconnect from the database and exits <code>ij</code>.</p>
<p><strong>RESTART THE NETWORK SERVER NOW</strong></p>
<p>After restarting, let’s see if it worked. Connect with <code>sa_resiste</code> and no password. Connection will get authentication failure.</p>
<pre><code class="bash">ij> connect 'jdbc:derby://localhost:11528/resiste' user 'sa_resiste';
ERROR 08004: Connection authentication failure occurred. Reason: Userid or password invalid.
</code></pre>
<p>Now connect with <code>sa_resiste</code> and password. Connection will succeed.</p>
<pre><code class="bash">ij> connect 'jdbc:derby://localhost:11528/resiste' user 'sa_resiste' password 'derby123';
ij>
</code></pre>
<p>Good! The admin user is now created. Next we’ll use the admin user to create a table. This table will be used to validate the permissions of the application-level user we’ll create later.</p>
<h1 id="createtesttable">Create Test Table</h1>
<p>Now we are going to use the admin user to create a test table. We will do this for a couple reasons.</p>
<ol>
<li>Verify the admin user has all permissions and is able to execute these SQL commands.</li>
<li>Verify the permissions of the application-level user we’ll create later.</li>
</ol>
<pre><code class="bash">ij> connect 'jdbc:derby://localhost:11528/resiste' user 'sa_resiste' password 'derby123';
ij> create schema testing;
ij> set schema testing;
ij> create table names (full_name varchar(100));
ij> insert into names values ('rita red');
ij> select * from names;
FULL_NAME
----------------------------------------------------------------------------------------------------
rita red
ij> disconnect;
</code></pre>
<p>Next let’s create the application-level user.</p>
<h1 id="createtheapplicationuser">Create the Application User</h1>
<p>Now for the fun stuff. Let’s create an application-level user. This will be a user with permission limited to only the operations an application is able to perform. For example, if your Microservice is only going to GET data, then the application-level user should only have SELECT permissions on the database table. We will test the application-level user’s permission, but first let’s create the user.</p>
<pre><code class="bash">ij> connect 'jdbc:derby://localhost:11528/resiste' user 'sa_resiste' password 'derby123';
ij> CALL SYSCS_UTIL.SYSCS_CREATE_USER('oscar', 'orange');
ij> disconnect;
ij> exit;
</code></pre>
<blockquote>
<p><strong>NOTE</strong>
Remember, oscar’s password will go stale in 31 days by default. However, if you have a derby.properties file with the derby.authentication.native.passwordLifetimeMillis property set, then it will go stale after whatever time is set by that property. There is no other way to change the time it takes for the password to go stale.</p>
</blockquote>
<p><strong>RESTART THE NETWORK SERVER NOW</strong></p>
<p>After restarting, let’s see if it worked. Connect with <code>oscar</code>. Connection will succeed, but, <code>oscar</code> won’t have the permission to read the test table.</p>
<pre><code class="bash">ij> connect 'jdbc:derby://localhost:11528/resiste' user 'oscar' password 'orange';
ij> select * from testing.names;
ERROR 42502: User 'OSCAR' does not have SELECT permission on column 'FULL_NAME' of table 'TESTING'.'NAMES'.
ij> disconnect;
</code></pre>
<p>Even though the SELECT statement failed, failure means a successful test. <code>oscar</code> has no permissions so should not be able to select from the test table. Let’s configure <code>oscar</code> next.</p>
<h1 id="configuretheapplicationuser">Configure the Application User</h1>
<p>Let’s set some permissions for <code>oscar</code>. Of course the <code>sa_resiste</code> admin user is required to do this.</p>
<pre><code class="bash">ij> connect 'jdbc:derby://localhost:11528/resiste' user 'sa_resiste' password 'derby123';
ij> set schema testing;
ij> grant select on names to oscar;
ij> disconnect;
</code></pre>
<p>This will give <code>oscar</code> only 1 permission: to select from TESTING.NAMES table. Let’s see if it worked.</p>
<pre><code class="bash">ij> connect 'jdbc:derby://localhost:11528/resiste' user 'oscar' password 'orange';
ij> select * from testing.names;
FULL_NAME
----------------------------------------------------------------------------------------------------
rita red
ij> disconnect;
</code></pre>
<p>Congratulations! You now have an application-level user with limited permissions in your Derby database.</p>
<h1 id="summary">Summary</h1>
<p>I hope you enjoyed learning how to do simple user administration with Derby.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-22054341091907921012018-03-02T14:52:00.000-06:002018-03-05T05:40:09.632-06:00My Thoughts on Jakarta EE<h1 id="theannouncement">The Announcement</h1>
<p>On February 26, 2018, I saw a post on Twitter saying EE4J tallied the results of the naming survey and <strong>Jakarta EE</strong> is the new brand for the open source enterprise software standard (Milinkovich, 2018). From what I’ve seen on Twitter and read on other blogs, the Java enterprise community has been very supportive of the re-branding. I am supportive as well (I voted for Jakarta EE) and will continue to evangelize Jakarta EE. Though this is a monumental step, the challenges for EE4J and the Jakarta EE brand are far from over. I’d like to share what I think are some of those challenges. You can skip to <a href="#tldr">tl;dr</a> to save time. They are:</p>
<ol>
<li>Getting the Re-branding to Stick</li>
<li>Release Cadence</li>
<li>Continued Emphasis on the EE Server</li>
</ol>
<h1 id="gettingthere-brandingtostick">Getting the Re-branding to Stick</h1>
<p>The announcement hadn’t even been a day old when Twitter posts started from recruiters looking for Jakarta EE experience (Ament, 2018). Java Enterprise software has been around for nearly 20 years, and over that time it has gone through previous cycles of renaming and re-branding. Here they are:</p>
<p>J2EE (1999) -> Java EE (2006) -> Jakarta EE (2018)</p>
<p>Re-branding is hard, and it can be argued that neither Sun nor Oracle did a good job promoting the brands. You can search Twitter for posts joking about how it has been over 10 years since the re-brand to Java EE but recruiters still advertise and look for “J2EE”. I am guilty of this as well. My LinkedIn profile has “J2EE” all over it so I can be found.</p>
<p>So how will EE4J fair promoting the Jakarta EE brand? I think success can be measured by management knowing about it. It’s sometimes dangerous when managers know things. But, if managers start asking, “are we are using Jakarta EE in new projects”, that’s success in branding.</p>
<blockquote>
<p><strong>NOTE</strong>
It’s OK if managers don’t know what Jarkarta EE is, as long as they know it’s the “thing to do” :)</p>
</blockquote>
<h1 id="releasecadence">Release Cadence</h1>
<p>Java SE is experiencing this challenge right now. The Java community griped about how long it took for Java to evolve and release new versions. Now that Oracle announced Java SE is on a 6 month release cycle (Evens, 2017) with limited long term support options (Azul, 2018), organizations don’t seem to know what to do (be careful what you wish for). Software is moving faster than infrastructure can keep up. </p>
<p>Release cadence can be a problem with Jakarta EE as well. The Java enterprise community may get its wish with rapid releases of Jakarta EE. But if this happens, I don’t think the releases will get the fan fair the community expects. Infrastructure won’t be able to keep up. Organizations will keep to their multi-year upgrade plans meaning long periods of time between server upgrades. Jakarta EE developers will be stuck using old standards like Java EE developers are stuck now.</p>
<p>A rapid release cadence for Jakarta EE may even cause more harm than good. As it is now, if an organization is upgrading servers every few years, the <em>perceived</em> risk of the upgrade is somewhat reduced because the standard may change by only 1 number: Java EE 6 to 7. On paper, that doesn’t look <em>too risky</em>. But if there have been multiple release over the years and the change is from Jakarta EE 9 to 14, the <em>perceived risk</em> is now YIKES!</p>
<p>I think Jakarta EE can support a rapid release cadence. However, to do this it has to face what I consider to be its most daunting challenge: the server.</p>
<h1 id="continuedemphasisontheeeserver">Continued Emphasis on the EE Server</h1>
<p>The final and most glaring challenge EE4J faces is continuing to remain server-technology focused. Will Jakarta EE continue to focus on the standards to create an “EE Server”? I hope not!</p>
<p>20 years ago, the expectation was to install a “heavy” server (WebLogic, WebSphere) and be able to ride that installation with very few changes for 3–5 years and you’re all good. Even security patches over those years were very limited; it was a different era! That’s the way infrastructure worked then, and if we are honest with ourselves, it’s the way infrastructure works now. Infrastructure is a huge impediment to software development. Developers want to use the latest-and-greatest, but infrastructure does not want to “take the risk”. Upgrading server software is always painful: lots of testing, lots of things breaking. So infrastructure avoids upgrades as much as it can.</p>
<p>The development community has gotten around this infrastructure problem by moving functionality <em>out</em> of the server and <em>into</em> the application, hence the Spring framework. Spring is successful precisely because of this infrastructure problem. Infrastructure typically does not care what’s <em>inside</em> the applications running on the server - the applications can change and upgrade as much as they want - just so long as the server itself doesn’t have to change. Is your production WebSphere installation 10 years old? “No problem,” developer’s say, “We aren’t really using anything the server provides anyway.”</p>
<p>Jakarta EE needs to evolve into a standard for an enterprise <strong>framework</strong> vs. an enterprise <strong>server</strong>. Spring, Hibernate, and other open source project can continue to push innovation forward. Organizations that want to take the risk and use those bleeding-edge technologies can do so. Once proven, their innovations can be incorporated into the Jakarta EE standard <strong>framework</strong> for the rest of us. Jakarta EE developers can use the new features added to the <strong>framework</strong> as quickly as their next product release by a simple POM <version> update…no need to install a new server. As an added bonus, if Jakarta EE sticks to TCK requirements and backward compatibility, Jakarta EE developers will have the confidence things won’t break when they change that often feared <version> number.</p>
<p>If Jakarta EE evolves into a standard for an enterprise <strong>framework</strong> (I hope so), Jakarta EE applications still need to run in something. Perhaps keeping to the current trend of using the Servlet container (Tomcat, Jetty) would be sufficient. But whatever form it takes, the expectation should be that the “server” would be able to sit for 10 years with minimal updates and not get in the way of the applications running in them. When the EE4J charter was under review, I believe there was some discussion on using the word “runtime” instead of “server”. This is appropriate. Have a Jakarta EE runtime (JEER) that can run for years without requiring major upgrades. </p>
<h1 id="tldr">tl;dr</h1>
<p>I fully support Jakarta EE as a brand and will continue to evangelize the technology as an open standard for enterprise development.</p>
<p>Successfully branding Jakarta EE will be a challenge. Branding will be successful if managers start asking, “Are we using Jakarta EE”? Jakarta EE should be a buzz word and the “thing to use” even though people may have no idea what it is. Sound familiar?</p>
<p>Release cadence may backfire on Jakarta EE because upgrading server installations will continue to move a lot slower.</p>
<p>Jakarta EE needs to evolve into a enterprise standard <strong>framework</strong>, deployable as part of an application vs. an enterprise standard <strong>server</strong> that has to be installed or upgraded on a box. The former results in speed, agility, and innovation. The latter results in stagnation.</p>
<p>Thanks for reading.</p>
<h1 id="references">References</h1>
<p>Milinkovich, M., (2018, February 26). And the Name Is…[Blog Post]. Retrieved from <a href="https://mmilinkov.wordpress.com/2018/02/26/and-the-name-is/">https://mmilinkov.wordpress.com/2018/02/26/and-the-name-is/</a>.</p>
<p>Ament, J. D., (2018, February 26). This just in [Tweet]. Retrieved from <a href="https://twitter.com/JohnAment/status/968324168264704000">https://twitter.com/JohnAment/status/968324168264704000</a>.</p>
<p>Evans, B., (2017, September 6). Java to Move to 6-Monthly Release Cadence [Blog Post]. Retrieved from <a href="https://www.infoq.com/news/2017/09/Java6Month">https://www.infoq.com/news/2017/09/Java6Month</a>.</p>
<p>Azul Systems, (2018, January 17). Azul Systems Announces Enhanced Java SE Support Plans [Blog Post]. Retrieved from <a href="https://globenewswire.com/news-release/2018/01/17/1295592/0/en/Azul-Systems-Announces-Enhanced-Java-SE-Support-Plans.html">https://globenewswire.com/news-release/2018/01/17/1295592/0/en/Azul-Systems-Announces-Enhanced-Java-SE-Support-Plans.html</a>.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-9011907345233409392018-01-10T08:16:00.000-06:002018-01-10T11:52:28.759-06:00Bootstrap CDI 2.0 in Java SE<h1 id="abstract">Abstract</h1>
<p>This is a quick reference for bootstrapping CDI 2.0 in a Java SE application.</p>
<h1 id="cdi2.0jsr365">CDI 2.0 (JSR 365)</h1>
<h2 id="mavendependency">Maven Dependency</h2>
<pre><code class="xml"><dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-core</artifactId>
<version>3.0.2.Final</version>
</dependency>
</code></pre>
<h2 id="beans.xml">beans.xml</h2>
<p>File location is <code>/META-INF/beans.xml</code>. This file is optional, though I’ve found it’s still good to have.</p>
<pre><code class="xml"><beans version="2.0"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
bean-discovery-mode="all"
>
<scan>
<exclude name="org.jboss.weld.**" />
</scan>
</beans>
</code></pre>
<h2 id="javaseapplication">Java SE Application</h2>
<pre><code class="java">package org.ferris.cdi.example.main;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
/**
* The main() method for this application
*
* @author <a href="mailto:mjremijan@yahoo.com">Mike Remijan</a>
*/
public class Main {
public static void main(String[] args) {
SeContainer container
= SeContainerInitializer.newInstance().initialize();
Main main
= container.select(Main.class).get();
//. . .
}
}
</code></pre>
<h1 id="summary">Summary</h1>
<p>That’s it…enjoy!</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-14559741059677327712017-12-30T14:40:00.000-06:002017-12-30T14:40:23.528-06:00JDBC Connections Cheat Sheet<h1 id="abstract">Abstract</h1>
<p>This is a quick reference for JDBC connections for common databases. I seem to have to lookup this information a lot, so I figured it be good to have a reference all in one place.</p>
<h1 id="derby">Derby</h1>
<pre><code class="xml"><dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.11.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.11.1.1</version>
<scope>test</scope>
</dependency>
</code></pre>
<h2 id="embeddedin-memory">Embedded (in-memory)</h2>
<pre><code class="java">Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
String connectionUrl
= "jdbc:derby:C:/My Databases/Derby/Test;user=;password=;create=true";
Connection conn
= DriverManager.getConnection(connectionUrl);
</code></pre>
<h2 id="remote">Remote</h2>
<pre><code class="java">Class.forName("org.apache.derby.jdbc.ClientDriver");
String connectionUrl
= "jdbc:derby://localhost:1527/widget";
String user = "sa";
String pass = "sa";
Connection conn
= DriverManager.getConnection(connectionUrl, user, pass);
</code></pre>
<h1 id="postgesql">PostgeSQL</h1>
<pre><code class="xml"><dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.1.4.jre7</version>
<scope>test</scope>
</dependency>
</code></pre>
<pre><code class="java">Class.forName("org.postgresql.Driver");
String connectionUrl
= "jdbc:postgresql://localhost:5432/widget";
String user = "widgetapp";
String pass = "widgetapp";
Connection conn
= DriverManager.getConnection(connectionUrl, user, pass);
</code></pre>
<h1 id="oracle">Oracle</h1>
<p>Download JDBC drivers from <a href="http://www.oracle.com/technetwork/database/features/jdbc/index.html">http://www.oracle.com/technetwork/database/features/jdbc/index.html</a></p>
<pre><code class="xml"><dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4</version>
<scope>system</scope>
<systemPath>${basedir}/lib/ojdbc6.jar</systemPath>
</dependency>
</code></pre>
<pre><code class="java">Class.forName("oracle.jdbc.driver.OracleDriver");
String SID
= "xe";
String connectionUrl
= "jdbc:oracle:thin:@localhost:1521:" + SID;
String user = "hr";
String pass = "hr";
Connection conn
= DriverManager.getConnection(connectionUrl, user, pass);
</code></pre>
<h1 id="summary">Summary</h1>
<p>That’s it…enjoy!</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com2tag:blogger.com,1999:blog-383035171837618940.post-78668597756161301032017-12-22T09:13:00.000-06:002018-01-26T16:09:56.989-06:00Java EE Deployment Descriptor and XML Reference<h1 id="abstract">Abstract</h1>
<p>This is a quick reference for the most used Java EE deployment descriptors and XML documents. Most developers do not know the versions of these files change with EE versions and that EE servers use the versions of these files to determine what EE standard to apply to your application. So, as you migrate your application to newer application servers, make sure to update the versions of the Java EE deployment descriptors and XML documents to take advantage of the new EE features.</p>
<h1 id="web.xml">web.xml</h1>
<pre><code class="xml"><!-- EE 8 -->
<web-app version="4.0"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">
</code></pre>
<pre><code class="xml"><!-- EE 7 -->
<web-app version="3.1"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
</code></pre>
<pre><code class="xml"><!-- EE 6 -->
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
</code></pre>
<h1 id="beans.xml">beans.xml</h1>
<pre><code class="xml"><!-- EE 8 -->
<beans version="2.0"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" bean-discovery-mode="all">
</code></pre>
<pre><code class="xml"><!-- EE 7 -->
<beans
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
</code></pre>
<pre><code class="xml"><!-- EE 6 -->
<beans
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd" -->
</code></pre>
<h1 id="summary">Summary</h1>
<p>That’s it…enjoy!</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-47018299336722794832017-12-22T08:17:00.000-06:002017-12-22T08:18:39.525-06:00Choosing Java Cryptographic Algorithms Part 3 - Public/Private key asymmetric encryption<h1 id="abstract">Abstract</h1>
<p>This is the 3rd of a three-part blog series covering Java cryptographic algorithms. The series covers how to implement the following:</p>
<ol>
<li><a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_20.html" title="Hashing with SHA-512">Hashing with <strong>SHA–512</strong></a></li>
<li><a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_22.html" title="Single-key symmetric encryption with AES-256">Single-key symmetric encryption with <strong>AES–256</strong></a></li>
<li><a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_5.html" title="Public/Private key asymmetric encryption with RSA-4096">Public/Private key asymmetric encryption with <strong>RSA–4096</strong></a></li>
</ol>
<p>This 3rd post details how to implement public/private key, asymmetric, RSA–4096 encryption. Let’s get started.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>Java 1.8.0_152_x64</li>
<li>NetBeans 8.2 (Build 201609300101)</li>
<li>Maven 3.0.5 (Bundled with NetBeans)</li>
</ul>
<h1 id="download">Download</h1>
<p>Visit <a href="https://github.com/mjremijan" title="My Github page link">my GitHub Page</a> to see all of my open source projects. The code for this post is located in project: <a href="https://github.com/mjremijan/thoth-cryptography" title="thoth-cryptography project link">thoth-cryptography</a></p>
<h1 id="asymmetricencryption">Asymmetric Encryption</h1>
<h2 id="about">About</h2>
<p>Asymmetric algorithms are based on 2 keys: a public key and a private key. The public key is responsible for encryption and the private key is responsible for decryption. The public key can be freely distributed. With the public key, any client can encrypt a message which only you - with the private key - can decrypt (Asymmetric algorithms, n.d. para. 3). </p>
<p>Asymmetric algorithms are the workhorse of the Internet. Protocols like SSH, OpenPGP, SSL, and TLS rely on asymmetric algorithms (Rouse, 2016, para. 2). Anyone who uses a web browser for something like online banking inherently knows the importance of asymmetric algorithms.</p>
<p>Research done as of today seems to indicate the best and most secure public/private key, asymmetric, encryption algorithm is the following (Sheth, 2017, “Choosing the correct algorithm”, para.2):</p>
<ol>
<li>Algorithm: <strong>RSA</strong></li>
<li>Mode: <strong>ECB</strong> // It’s really NONE but ECB is needed to get Java to work.</li>
<li>Padding: <strong>OAEPWithSHA–512AndMGF1Padding</strong></li>
<li>Key size: <strong>4096 bit</strong></li>
</ol>
<p>RSA isn’t a block cipher so ECB mode doesn’t make much sense, but, <code>ECB</code> is needed to make Java work even though the mode isn’t used under the covers (Brightwell, 2015). OAEP provides a high level of randomness and padding. Let’s take a look at an example.</p>
<h1 id="example">Example</h1>
<p>Listing 1 is the <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-04-encryption-asymmetric-rsa4096/src/test/java/org/thoth/crypto/asymmetric/RsaTest.java" title="RsaTest.java">RsaTest.java</a> unit test. It is a full demonstration on the following:</p>
<ol>
<li>Generate and store an RSA 4096-bit key</li>
<li>RSA Encryption</li>
<li>RSA Decryption</li>
</ol>
<p>Listing 2 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-04-encryption-asymmetric-rsa4096/src/main/java/org/thoth/crypto/asymmetric/RsaKeyPairProducer.java" title="RsaKeyPairProducer.java">RsaKeyPairProducer.java</a>. This is a helper class which is responsible for producing a new <code>KeyPair</code>. The <code>KeyPair</code> contains both the <code>PublicKey</code> and <code>PrivateKey</code>.</p>
<p>Listing 3 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-04-encryption-asymmetric-rsa4096/src/main/java/org/thoth/crypto/asymmetric/RsaPrivateKeyProducer.java" title="RsaPrivateKeyProducer.java">RsaPrivateKeyProducer.java</a>. This is a helper class which is responsible for reproducing a <code>PrivateKey</code> from a <code>byte[]</code>. </p>
<p>Listing 4 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-04-encryption-asymmetric-rsa4096/src/main/java/org/thoth/crypto/asymmetric/RsaPublicKeyProducer.java" title="RsaPublicKeyProducer.java">RsaPublicKeyProducer.java</a>. This is a helper class which is responsible for reproducing a <code>PublicKey</code> from a <code>byte[]</code>.</p>
<p>Listing 5 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-04-encryption-asymmetric-rsa4096/src/main/java/org/thoth/crypto/io/ByteArrayWriter.java" title="ByteArrayWriter.java">ByteArrayWriter.java</a> and Listing 6 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-04-encryption-asymmetric-rsa4096/src/main/java/org/thoth/crypto/io/ByteArrayReader.java" title="ByteArrayReader.java">ByteArrayReader.java</a>. These are helper classes responsible for reading and writing a <code>byte[]</code> to a file. It’s up to you to determine how to store the <code>byte[]</code> of your keys, but it needs to be stored securely somewhere (file, database, git repository, etc.).</p>
<p>Listing 7 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-04-encryption-asymmetric-rsa4096/src/main/java/org/thoth/crypto/asymmetric/RsaEncrypter.java" title="RsaEncrypter.java">RsaEncrypter.java</a>. This is a helper class which is responsible for encryption. </p>
<p>Finally, listing 8 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-04-encryption-asymmetric-rsa4096/src/main/java/org/thoth/crypto/asymmetric/RsaDecrypter.java" title="RsaDecrypter.java">RsaDecrypter.java</a>. This is a helper class which is responsible for decryption. </p>
<h1 id="listing1-rsatest.javaclass">Listing 1 - RsaTest.java class</h1>
<pre><code class="java">package org.thoth.crypto.asymmetric;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.thoth.crypto.io.ByteArrayReader;
import org.thoth.crypto.io.ByteArrayWriter;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaTest {
static Path privateKeyFile;
static Path publicKeyFile;
@BeforeClass
public static void beforeClass() throws Exception {
// Store the PrivateKey and PublicKey bytes in the ./target
// diretory. Do this so it will be ignore by source control.
// We don't want this file committed.
privateKeyFile
= Paths.get("./target/RsaPrivate.key").toAbsolutePath();
publicKeyFile
= Paths.get("./target/RsaPublic.key").toAbsolutePath();
// Create KeyPair for RSA
KeyPair keyPair
= new RsaKeyPairProducer().produce();
// Store the PrivateKey bytes. This is what
// you want to keep absolutely safe
{
ByteArrayWriter writer = new ByteArrayWriter(privateKeyFile);
writer.write(keyPair.getPrivate().getEncoded());
}
// Store the PublicKey bytes. This you
// can freely distribute so others can
// encrypt messages which you can then
// decrypt with the PrivateKey you keep safe.
{
ByteArrayWriter writer = new ByteArrayWriter(publicKeyFile);
writer.write(keyPair.getPublic().getEncoded());
}
}
@Test
public void encrypt_and_decrypt() throws Exception {
// setup
PrivateKey privateKey
= new RsaPrivateKeyProducer().produce(
new ByteArrayReader(privateKeyFile).read()
);
PublicKey publicKey
= new RsaPublicKeyProducer().produce(
new ByteArrayReader(publicKeyFile).read()
);
RsaDecrypter decrypter
= new RsaDecrypter(privateKey);
RsaEncrypter encrypter
= new RsaEncrypter(publicKey);
String toEncrypt
= "encrypt me";
// run
byte[] encryptedBytes
= encrypter.encrypt(toEncrypt);
System.out.printf("Encrypted %s%n", new String(encryptedBytes,"UTF-8"));
String decrypted
= decrypter.decrypt(encryptedBytes);
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
}
</code></pre>
<h1 id="listing2-rsakeypairproducer.javaclass">Listing 2 - RsaKeyPairProducer.java class</h1>
<pre><code class="java">package org.thoth.crypto.asymmetric;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaKeyPairProducer {
/**
* Generates a new RSA-4096 bit {@code KeyPair}.
*
* @return {@code KeyPair}, never null
* @throws RuntimeException All exceptions are caught
* and re-thrown as {@code RuntimeException}
*/
public KeyPair produce() {
KeyPairGenerator keyGen;
try {
keyGen = KeyPairGenerator.getInstance("RSA");
//keyGen.initialize(3072);
keyGen.initialize(4096);
return keyGen.generateKeyPair();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
</code></pre>
<h1 id="listing3-rsaprivatekeyproducer.javaclass">Listing 3 - RsaPrivateKeyProducer.java class</h1>
<pre><code class="java">package org.thoth.crypto.asymmetric;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaPrivateKeyProducer {
/**
* Regenerates a previous RSA {@code PrivateKey}.
*
* @param encodedByteArrayForPrivateKey The bytes this method
* will use to regenerate a previously created {@code PrivateKey}
*
* @return {@code PrivateKey}, never null
* @throws RuntimeException All exceptions are caught
* and re-thrown as {@code RuntimeException}
*/
public PrivateKey produce(byte[] encodedByteArrayForPrivateKey) {
try {
PrivateKey privateKey = KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(encodedByteArrayForPrivateKey));
return privateKey;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
</code></pre>
<h1 id="listing4-rsapublickeyproducer.javaclass">Listing 4 - RsaPublicKeyProducer.java class</h1>
<pre><code class="java">package org.thoth.crypto.asymmetric;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaPublicKeyProducer {
/**
* Regenerates a previous RSA {@code PublicKey}.
*
* @param encodedByteArrayForPublicKey The bytes this method
* will use to regenerate a previously created {@code PublicKey}
*
* @return {@code PublicKey}, never null
* @throws RuntimeException All exceptions are caught
* and re-thrown as {@code RuntimeException}
*/
public PublicKey produce(byte[] encodedByteArrayForPublicKey) {
try {
PublicKey publicKey = KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(encodedByteArrayForPublicKey));
return publicKey;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
</code></pre>
<h1 id="listing5-bytearraywriter.javaclass">Listing 5 - ByteArrayWriter.java class</h1>
<pre><code class="java">package org.thoth.crypto.io;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class ByteArrayWriter {
protected Path outputFile;
private void initOutputFile(Path outputFile) {
this.outputFile = outputFile;
}
private void initOutputDirectory() {
Path outputDirectory = outputFile.getParent();
if (!Files.exists(outputDirectory)) {
try {
Files.createDirectories(outputDirectory);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public ByteArrayWriter(Path outputFile) {
initOutputFile(outputFile);
initOutputDirectory();
}
public void write(byte[] bytesArrayToWrite) {
try (
OutputStream os
= Files.newOutputStream(outputFile);
PrintWriter writer
= new PrintWriter(os);
){
for (int i=0; i<bytesArrayToWrite.length; i++) {
if (i>0) {
writer.println();
}
writer.print(bytesArrayToWrite[i]);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
</code></pre>
<h1 id="listing6-bytearrayreader.javaclass">Listing 6 - ByteArrayReader.java class</h1>
<pre><code class="java">package org.thoth.crypto.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Scanner;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class ByteArrayReader {
protected Path inputFile;
public ByteArrayReader(Path inputFile) {
this.inputFile = inputFile;
}
public byte[] read() {
try (
Scanner scanner
= new Scanner(inputFile);
ByteArrayOutputStream baos
= new ByteArrayOutputStream();
){
while (scanner.hasNext()) {
baos.write(Byte.parseByte(scanner.nextLine()));
}
baos.flush();
return baos.toByteArray();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
</code></pre>
<h1 id="listing7-rsaencrypter.javaclass">Listing 7 - RsaEncrypter.java class</h1>
<pre><code class="java">package org.thoth.crypto.asymmetric;
import java.security.PublicKey;
import javax.crypto.Cipher;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaEncrypter {
protected RsaCipher cipher;
public RsaEncrypter(PublicKey key) {
this.cipher = new RsaCipher(Cipher.ENCRYPT_MODE, key);
}
public byte[] encrypt(String message) {
try {
return cipher
.update(message.getBytes("UTF-8"))
.doFinal()
;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
</code></pre>
<h1 id="listing8-rsadecrypter.javaclass">Listing 8 - RsaDecrypter.java class</h1>
<pre><code class="java">package org.thoth.crypto.asymmetric;
import java.security.PrivateKey;
import javax.crypto.Cipher;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaDecrypter {
protected RsaCipher cipher;
public RsaDecrypter(PrivateKey key) {
this.cipher = new RsaCipher(Cipher.DECRYPT_MODE, key);
}
public String decrypt(byte[] message) {
try {
return new String(
cipher.update(message).doFinal()
, "UTF-8"
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
</code></pre>
<h1 id="summary">Summary</h1>
<p>Encryption isn’t easy. And easy examples will result in implementations with security vulnerabilities for your application. If you need a public/private key, asymmetric, encryption algorithm, use RSA/ECB/OAEPWithSHA–512AndMGF1Padding, with a 4096 bit key.</p>
<h1 id="references">References</h1>
<p>Sheth, M. (2017, April 18). Encryption and Decryption in Java Cryptography. Retrieved from <a href="https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography">https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography</a>.</p>
<ul>
<li>Best algorithms, modes, and paddings</li>
<li>Use 4096-bit key</li>
</ul>
<p>Brightwell, W., poncho. (2015, May 4). Is ECB mode safe to use with RSA encryption?. Retrieved from <a href="https://crypto.stackexchange.com/questions/25420/is-ecb-mode-safe-to-use-with-rsa-encryption">https://crypto.stackexchange.com/questions/25420/is-ecb-mode-safe-to-use-with-rsa-encryption</a>.</p>
<ul>
<li>ECB isn’t applicable; Java doesn’t use it under the covers.</li>
<li>RSA uses randomness and the padding to avoid ECB issue of the same plaintext generating the same ciphertext</li>
</ul>
<p>Marilena. (2016, November 29). Java - Asymmetric Cryptography example. Retrieved from <a href="https://www.mkyong.com/java/java-asymmetric-cryptography-example/">https://www.mkyong.com/java/java-asymmetric-cryptography-example/</a>.</p>
<ul>
<li>Writing the public/private keys to file</li>
<li>Reading the public/private keys from file</li>
<li>Encrypt & Decrypt</li>
</ul>
<p>Key size. (2017, October 12). Wikipedia. Retrieved from <a href="https://en.wikipedia.org/wiki/Key_size">https://en.wikipedia.org/wiki/Key_size</a>.</p>
<ul>
<li>2048-bit keys are sufficient until 2030</li>
<li>An RSA key length of 3072 bits should be used if security is required beyond 2030</li>
</ul>
<p>user4982. (2013, November 4). How are IVs used in association with RSA Encryption?. Retrieved from <a href="https://crypto.stackexchange.com/questions/11403/how-are-ivs-used-in-association-with-rsa-encryption">https://crypto.stackexchange.com/questions/11403/how-are-ivs-used-in-association-with-rsa-encryption</a>.</p>
<ul>
<li>IV values are not used with RSA</li>
</ul>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com1tag:blogger.com,1999:blog-383035171837618940.post-58450759836562022282017-12-22T07:55:00.002-06:002022-11-22T10:27:33.634-06:00Choosing Java Cryptographic Algorithms Part 2 - Single key symmetric encryption<h1 id="abstract">Abstract</h1>
<p>This is the 2nd of a three-part blog series covering Java cryptographic algorithms. The series covers how to implement the following:</p>
<ol>
<li><a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_20.html" title="Hashing with SHA-512">Hashing with <strong>SHA-512</strong></a></li>
<li><a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_22.html" title="Single-key symmetric encryption with AES-256">Single-key symmetric encryption with <strong>AES-256</strong></a></li>
<li><a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_5.html" title="Public/Private key asymmetric encryption with RSA-4096">Public/Private key asymmetric encryption with <strong>RSA-4096</strong></a></li>
</ol>
<p>This 2nd post details how to implement single key, symmetric, AES-256 encryption. Let’s get started.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>Java 1.8.0_152_x64</li>
<li><a href="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html" title="Cryptography extension download link"><strong>Java Cryptography Extension (JCE) Unlimited Strength</strong></a></li>
<li>NetBeans 8.2 (Build 201609300101)</li>
<li>Maven 3.0.5 (Bundled with NetBeans)</li>
</ul>
<blockquote>
<p><strong>NOTE</strong>
As of Java 1.8.0_161, unlimited cryptography is enabled by default. This means if you are using Java 1.8.0_161 or later, you do not need to install Java Cryptography Extension (JCE) Unlimited Strength separately. See the <a href="https://www.oracle.com/technetwork/java/javase/8u161-relnotes-4021379.html#JDK-8170157" title="release notes">Java 1.8.0_161 release notes</a></p>
</blockquote>
<h1 id="acknowledgments">Acknowledgments</h1>
<p>Thanks to Peter Jakobsen. In March 2022, he identified an error in my <code>Aes#encrypt()</code> method. The error caused encryption/decryption process to fail when the string being encrypted was greater than 15 characters long. I did not have unit test for this case.</p>
<p>At the time, I did not have availability to look into this problem. Peter posted the question to <a href="https://stackoverflow.com/questions/71422498/javax-crypto-aeadbadtagexception-tag-mismatch-when-password-length-16">https://stackoverflow.com/questions/71422498/javax-crypto-aeadbadtagexception-tag-mismatch-when-password-length-16</a>. Additional thanks to Topaco on Stack Overflow for providing comments.</p>
<p>In November 2022, I was able to update my <code>Aes</code> class to address the problem Peter identified. Using the Template Method Design Pattern, I now have two sub-classes of <code>Aes</code> demonstrating both the <code>multiple-part</code> and <code>single-part</code> encryption operations. I have updated this blog to reflect these new classes.</p>
<h1 id="download">Download</h1>
<p>Visit <a href="https://github.com/mjremijan" title="My Github page link">my GitHub Page</a> to see all of my open source projects. The code for this post is located in project: <a href="https://github.com/mjremijan/thoth-cryptography" title="thoth-cryptography project link">thoth-cryptography</a></p>
<h1 id="symmetricencryption">Symmetric Encryption</h1>
<h2 id="about">About</h2>
<p>Symmetric encryption algorithms are based on a single key. This one key is used for both encryption and decryption. As such, symmetric algorithms should only be used where strict controls are in place to protect the key.</p>
<p>Symmetric algorithms are commonly used for encryption and decryption of data in secured environments. A good example of this is securing Microservice communication. If an OAuth-2/JWT architecture is out of scope, the API Gateway can use a symmetric algorithm’s single key to encrypt a token. This token is then passed to other Microservices. The other Microservices use the same key to decrypt token. Another good example are hyperlinks embedded in emails. The hyperlinks in emails contain an encoded token which allow automatic login request processing when the hyperlink is clicked. This token is a strongly encrypted value generated by a symmetric algorithm so it can only be decoded on the application server. And of course, anytime passwords or credentials of any kind need to be protected, a symmetric algorithm is used to encrypt them and the bytes can later be decrypted with the same key.</p>
<p>Research done as of today seems to indicate the best and most secure single key, symmetric, encryption algorithm is the following (Sheth, 2017, “Choosing the correct algorithm”, para.2):</p>
<ol>
<li>Algorithm: <strong>AES</strong></li>
<li>Mode: <strong>GCM</strong></li>
<li>Padding: <strong>PKCS5Padding</strong></li>
<li>Key size: <strong>256 bit</strong></li>
<li>IV size: <strong>96 bit</strong></li>
</ol>
<p>AES-256 uses a 256-bit key which <strong>requires</strong> installation of the <a href="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html" title="Cryptography extension download link">Java Cryptography Extension (JCE) Unlimited Strength</a> package. Let’s take a look at an example.</p>
<blockquote>
<p><strong>NOTE</strong>
The <a href="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html" title="Cryptography extension download link">Java Cryptography Extension (JCE) Unlimited Strength</a> package is required for 256-bit keys. If it’s not installed, 128-bit keys are the max.</p>
</blockquote>
<h1 id="example">Example</h1>
<p>If you don’t already have it, download and install the <a href="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html" title="Cryptography extension download link">Java Cryptography Extension (JCE) Unlimited Strength</a> package. It is required to use 256-bit keys. Otherwise, the example below must be updated to use a 128-bit key.</p>
<p>Listing 1 and Listing 2 are the unit tests <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-03-encryption-symmetric-aes256/src/test/java/org/thoth/crypto/symmetric/AesUsingSinglePartEncryptionTest.java" title="AesUsingSinglePartEncryptionTest.java">AesUsingSinglePartEncryptionTest.java</a> and <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-03-encryption-symmetric-aes256/src/test/java/org/thoth/crypto/symmetric/AesUsingMultiplePartEncryptionTest.java" title="AesUsingMultiplePartEncryptionTest.java">AesUsingMultiplePartEncryptionTest.java</a>. Both unit tests are full demonstrations of the following:</p>
<ol>
<li>Generate and store an AES 256-bit key</li>
<li>AES Encryption</li>
<li>AES Decryption</li>
</ol>
<p>One demonstrates multiple-part encryption and the other demonstrates single-part encryption. The difference between the two is how the <code>Cipher#update</code> and <code>Cipher#doFinal</code> methods are used to get the encrypted bytes.</p>
<p>Listing 3 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-03-encryption-symmetric-aes256/src/main/java/org/thoth/crypto/symmetric/AesSecretKeyProducer.java" title="AesSecretKeyProducer.java">AesSecretKeyProducer.java</a>. This is a helper class which is responsible for producing a new key or reproducing an existing key from a <code>byte[]</code>.</p>
<p>Listing 4 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-03-encryption-symmetric-aes256/src/main/java/org/thoth/crypto/io/ByteArrayWriter.java" title="ByteArrayWriter.java">ByteArrayWriter.java</a> and Listing 5 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-03-encryption-symmetric-aes256/src/main/java/org/thoth/crypto/io/ByteArrayReader.java" title="ByteArrayReader.java">ByteArrayReader.java</a>. These are helper classes responsible for reading and writing a <code>byte[]</code> to a file. It’s up to you to determine how to store the <code>byte[]</code> of your key, but it needs to be stored securely somewhere (file, database, git repository, etc.).</p>
<p>Listing 6 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-03-encryption-symmetric-aes256/src/main/java/org/thoth/crypto/symmetric/Aes.java" title="Aes.java">Aes.java</a>. This is a helper class which is responsible for both encryption and decryption. It is implemented as an <code>abstract</code> class so it can not be instantiated directly. Instead, one of its sub-classes need to be used. There are 2 sub-classes, one to demonstrate the single-part encryption operation and the other to demonstrate the multiple-part encryption operation.</p>
<blockquote>
<p><strong>NOTE</strong>
In reality, you probably won’t need an <code>abstract</code> class. Instead you’ll most likely create 1 class which uses the encryption operation (single-part or multiple-part) you want to use.</p>
</blockquote>
<p>Listing 7 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-03-encryption-symmetric-aes256/src/main/java/org/thoth/crypto/symmetric/AesUsingSinglePartEncryption.java" title="AesUsingSinglePartEncryption.java">AesUsingSinglePartEncryption.java</a>. This extends the <code>Aes</code> class and implements the <code>getEncryptedBytes()</code> method. Its implementation demonstrates the single-part encryption operation, which uses a single call to <code>Cipher#doFinal(byte[])</code> to get the encrypted bytes.</p>
<p>Listing 8 shows <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-03-encryption-symmetric-aes256/src/main/java/org/thoth/crypto/symmetric/AesUsingMultiplePartEncryption.java" title="AesUsingMultiplePartEncryption.java">AesUsingMultiplePartEncryption.java</a>. This extends the <code>Aes</code> class and implements the <code>getEncryptedBytes()</code> method. Its implementation demonstrates the multiple-part encryption operation which makes multiple calls to <code>Cipher#update(byte[])</code> and then a final call to <code>Cipher#doFinal()</code>. All the encrypted bytes are store along the way and returned after the call to <code>Cipher#doFinal()</code>.</p>
<h1 id="listing1-aesusingsinglepartencryptiontest.javaclass">Listing 1 - AesUsingSinglePartEncryptionTest.java class</h1>
<pre><code class="java">package org.thoth.crypto.symmetric;
import java.io.ByteArrayOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Optional;
import javax.crypto.SecretKey;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.thoth.crypto.io.ByteArrayReader;
import org.thoth.crypto.io.ByteArrayWriter;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class AesUsingSinglePartEncryptionTest {
static Path secretKeyFile;
@BeforeClass
public static void beforeClass() throws Exception {
// Store the SecretKey bytes in the ./target diretory. Do
// this so it will be ignore by source control. We don't
// want this file committed.
secretKeyFile
= Paths.get("./target/Aes256.key").toAbsolutePath();
// Generate a SecretKey for the test
SecretKey secretKey
= new AesSecretKeyProducer().produce();
// Store the byte[] of the SecretKey. This is the
// "private key file" you want to keep safe.
ByteArrayWriter writer = new ByteArrayWriter(secretKeyFile);
writer.write(secretKey.getEncoded());
}
@Test
public void encrypt_and_decrypt_using_same_Aes256_instance_long() {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aes
= new AesUsingSinglePartEncryption(secretKey);
String toEncrypt
= "encrypt me1, encrypt me2, encrypt me3, encrypt me4, encrypt me5, encrypt me6, encrypt me7, encrypt me8, encrypt me9, encrypt me10";
// run
byte[] encryptedBytes
= aes.encrypt(toEncrypt, Optional.empty());
String decrypted
= aes.decrypt(encryptedBytes, Optional.empty());
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
@Test
public void encrypt_and_decrypt_using_same_Aes256_instance_short() {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aes
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "encrypt me";
// run
byte[] encryptedBytes
= aes.encrypt(toEncrypt, Optional.empty());
String decrypted
= aes.decrypt(encryptedBytes, Optional.empty());
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
public void encrypt_and_decrypt_with_aad_using_same_Aes256_instance_short() {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aes
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "encrypt me aad";
// run
byte[] encryptedBytes
= aes.encrypt(toEncrypt, Optional.of("JUnit AAD"));
String decrypted
= aes.decrypt(encryptedBytes, Optional.of("JUnit AAD"));
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
public void encrypt_and_decrypt_with_aad_using_same_Aes256_instance_long() {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aes
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "encrypt me aad 1, encrypt me aad 2, encrypt me aad 3, encrypt me aad 4, encrypt me aad 5, encrypt me aad 6, encrypt me aad 7, encrypt me aad 8, encrypt me aad 9, encrypt me aad 10";
// run
byte[] encryptedBytes
= aes.encrypt(toEncrypt, Optional.of("JUnit AAD"));
String decrypted
= aes.decrypt(encryptedBytes, Optional.of("JUnit AAD"));
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
@Test
public void encrypt_and_decrypt_using_different_Aes256_instance_long()
throws Exception {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aesForEncrypt
= new AesUsingMultiplePartEncryption(secretKey);
Aes aesForDecrypt
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "encrypt me1, encrypt me2, encrypt me3, encrypt me4, encrypt me5, encrypt me6, encrypt me7, encrypt me8, encrypt me9, encrypt me10";
// run
byte[] encryptedBytes
= aesForEncrypt.encrypt(toEncrypt, Optional.empty());
ByteArrayOutputStream baos
= new ByteArrayOutputStream();
baos.write(encryptedBytes);
String decrypted
= aesForDecrypt.decrypt(baos.toByteArray(), Optional.empty());
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
@Test
public void encrypt_and_decrypt_using_different_Aes256_instance_short()
throws Exception {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aesForEncrypt
= new AesUsingMultiplePartEncryption(secretKey);
Aes aesForDecrypt
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "eNcryPt Me";
// run
byte[] encryptedBytes
= aesForEncrypt.encrypt(toEncrypt, Optional.empty());
ByteArrayOutputStream baos
= new ByteArrayOutputStream();
baos.write(encryptedBytes);
String decrypted
= aesForDecrypt.decrypt(baos.toByteArray(), Optional.empty());
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
@Test
public void foo() {
String s = "ABC";
int chunkSize = 5;
String[] chunks = s.split("(?<=\\G.{" + chunkSize + "})");
System.out.println(Arrays.toString(chunks));
}
}
</code></pre>
<h1 id="listing2-aesusingmultiplepartencryptiontest.javaclass">Listing 2 - AesUsingMultiplePartEncryptionTest.java class</h1>
<pre><code class="java">package org.thoth.crypto.symmetric;
import java.io.ByteArrayOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Optional;
import javax.crypto.SecretKey;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.thoth.crypto.io.ByteArrayReader;
import org.thoth.crypto.io.ByteArrayWriter;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class AesUsingMultiplePartEncryptionTest {
static Path secretKeyFile;
@BeforeClass
public static void beforeClass() throws Exception {
// Store the SecretKey bytes in the ./target diretory. Do
// this so it will be ignore by source control. We don't
// want this file committed.
secretKeyFile
= Paths.get("./target/Aes256.key").toAbsolutePath();
// Generate a SecretKey for the test
SecretKey secretKey
= new AesSecretKeyProducer().produce();
// Store the byte[] of the SecretKey. This is the
// "private key file" you want to keep safe.
ByteArrayWriter writer = new ByteArrayWriter(secretKeyFile);
writer.write(secretKey.getEncoded());
}
@Test
public void encrypt_and_decrypt_using_same_Aes256_instance_long() {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aes
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "encrypt me1, encrypt me2, encrypt me3, encrypt me4, encrypt me5, encrypt me6, encrypt me7, encrypt me8, encrypt me9, encrypt me10";
// run
byte[] encryptedBytes
= aes.encrypt(toEncrypt, Optional.empty());
String decrypted
= aes.decrypt(encryptedBytes, Optional.empty());
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
@Test
public void encrypt_and_decrypt_using_same_Aes256_instance_short() {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aes
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "encrypt me";
// run
byte[] encryptedBytes
= aes.encrypt(toEncrypt, Optional.empty());
String decrypted
= aes.decrypt(encryptedBytes, Optional.empty());
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
public void encrypt_and_decrypt_with_aad_using_same_Aes256_instance_short() {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aes
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "encrypt me aad";
// run
byte[] encryptedBytes
= aes.encrypt(toEncrypt, Optional.of("JUnit AAD"));
String decrypted
= aes.decrypt(encryptedBytes, Optional.of("JUnit AAD"));
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
public void encrypt_and_decrypt_with_aad_using_same_Aes256_instance_long() {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aes
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "encrypt me aad 1, encrypt me aad 2, encrypt me aad 3, encrypt me aad 4, encrypt me aad 5, encrypt me aad 6, encrypt me aad 7, encrypt me aad 8, encrypt me aad 9, encrypt me aad 10";
// run
byte[] encryptedBytes
= aes.encrypt(toEncrypt, Optional.of("JUnit AAD"));
String decrypted
= aes.decrypt(encryptedBytes, Optional.of("JUnit AAD"));
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
@Test
public void encrypt_and_decrypt_using_different_Aes256_instance_long()
throws Exception {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aesForEncrypt
= new AesUsingMultiplePartEncryption(secretKey);
Aes aesForDecrypt
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "encrypt me1, encrypt me2, encrypt me3, encrypt me4, encrypt me5, encrypt me6, encrypt me7, encrypt me8, encrypt me9, encrypt me10";
// run
byte[] encryptedBytes
= aesForEncrypt.encrypt(toEncrypt, Optional.empty());
ByteArrayOutputStream baos
= new ByteArrayOutputStream();
baos.write(encryptedBytes);
String decrypted
= aesForDecrypt.decrypt(baos.toByteArray(), Optional.empty());
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
@Test
public void encrypt_and_decrypt_using_different_Aes256_instance_short()
throws Exception {
// setup
SecretKey secretKey
= new AesSecretKeyProducer().produce(
new ByteArrayReader(secretKeyFile).read()
);
Aes aesForEncrypt
= new AesUsingMultiplePartEncryption(secretKey);
Aes aesForDecrypt
= new AesUsingMultiplePartEncryption(secretKey);
String toEncrypt
= "eNcryPt Me";
// run
byte[] encryptedBytes
= aesForEncrypt.encrypt(toEncrypt, Optional.empty());
ByteArrayOutputStream baos
= new ByteArrayOutputStream();
baos.write(encryptedBytes);
String decrypted
= aesForDecrypt.decrypt(baos.toByteArray(), Optional.empty());
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
@Test
public void foo() {
String s = "ABC";
int chunkSize = 5;
String[] chunks = s.split("(?<=\\G.{" + chunkSize + "})");
System.out.println(Arrays.toString(chunks));
}
}
</code></pre>
<h1 id="listing3-aessecretkeyproducer.javaclass">Listing 3 - AesSecretKeyProducer.java class</h1>
<pre><code class="java">package org.thoth.crypto.symmetric;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class AesSecretKeyProducer {
/**
* Generates a new AES-256 bit {@code SecretKey}.
*
* @return {@code SecretKey}, never null
* @throws RuntimeException All exceptions are caught and re-thrown as {@code RuntimeException}
*/
public SecretKey produce() {
KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
return secretKey;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Generates an AES-256 bit {@code SecretKey}.
*
* @param encodedByteArray The bytes this method will use to regenerate a previously created {@code SecretKey}
*
* @return {@code SecretKey}, never null
* @throws RuntimeException All exceptions are caught and re-thrown as {@code RuntimeException}
*/
public SecretKey produce(byte [] encodedByteArray) {
try {
return new SecretKeySpec(encodedByteArray, "AES");
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
</code></pre>
<h1 id="listing4-bytearraywriter.javaclass">Listing 4 - ByteArrayWriter.java class</h1>
<pre><code class="java">package org.thoth.crypto.io;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class ByteArrayWriter {
protected Path outputFile;
private void initOutputFile(Path outputFile) {
this.outputFile = outputFile;
}
private void initOutputDirectory() {
Path outputDirectory = outputFile.getParent();
if (!Files.exists(outputDirectory)) {
try {
Files.createDirectories(outputDirectory);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public ByteArrayWriter(Path outputFile) {
initOutputFile(outputFile);
initOutputDirectory();
}
public void write(byte[] bytesArrayToWrite) {
try (
OutputStream os
= Files.newOutputStream(outputFile);
PrintWriter writer
= new PrintWriter(os);
){
for (int i=0; i<bytesArrayToWrite.length; i++) {
if (i>0) {
writer.println();
}
writer.print(bytesArrayToWrite[i]);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
</code></pre>
<h1 id="listing5-bytearrayreader.javaclass">Listing 5 - ByteArrayReader.java class</h1>
<pre><code class="java">package org.thoth.crypto.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Scanner;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class ByteArrayReader {
protected Path inputFile;
public ByteArrayReader(Path inputFile) {
this.inputFile = inputFile;
}
public byte[] read() {
try (
Scanner scanner
= new Scanner(inputFile);
ByteArrayOutputStream baos
= new ByteArrayOutputStream();
){
while (scanner.hasNext()) {
baos.write(Byte.parseByte(scanner.nextLine()));
}
baos.flush();
return baos.toByteArray();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
</code></pre>
<h1 id="listing6-aes.javaclass">Listing 6 - Aes.java class</h1>
<pre><code class="java">package org.thoth.crypto.symmetric;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Optional;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public abstract class Aes {
// If you don't have the Java Cryptography Extension
// (JCE) Unlimited Strength packaged installed, use
// a 128 bit KEY_SIZE.
public static int KEY_SIZE = 256;
public static int IV_SIZE = 12; // 12bytes * 8 = 96bits
public static int TAG_BIT_SIZE = 128;
public static String ALGORITHM_NAME = "AES";
public static String MODE_OF_OPERATION = "GCM";
public static String PADDING_SCHEME = "PKCS5Padding";
protected SecretKey secretKey;
protected SecureRandom secureRandom;
protected Aes(SecretKey secretKey) {
this.secretKey = secretKey;
this.secureRandom = new SecureRandom();
}
public byte[] encrypt(String message, Optional<String> aad) {
try {
// Transformation specifies algortihm, mode of operation and padding
Cipher c = Cipher.getInstance(
String.format("%s/%s/%s",ALGORITHM_NAME,MODE_OF_OPERATION,PADDING_SCHEME)
);
// Generate IV
byte iv[] = new byte[IV_SIZE];
secureRandom.nextBytes(iv); // SecureRandom initialized using self-seeding
// Initialize GCM Parameters
GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_SIZE, iv);
// Init for encryption
c.init(Cipher.ENCRYPT_MODE, secretKey, spec, secureRandom);
// Add AAD tag data if present
aad.ifPresent(t -> {
try {
c.updateAAD(t.getBytes("UTF-8"));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// I demonstrate 2 different ways of getting the
// encrypted bytes. See the 2 sub-classes which
// implement the method of this abstract class.
ByteArrayOutputStream baos = getEncryptedBytes(message, c);
// Concatinate IV and encrypted bytes. The IV is needed later
// in order to to decrypt. The IV value does not need to be
// kept secret, so it's OK to encode it in the return value
//
// Create a new byte[] the combined length of IV and encryptedBytes
byte[] ivPlusEncryptedBytes = new byte[iv.length + baos.size()];
// Copy IV bytes into the new array
System.arraycopy(iv, 0, ivPlusEncryptedBytes, 0, iv.length);
// Copy encryptedBytes into the new array
System.arraycopy(baos.toByteArray(), 0, ivPlusEncryptedBytes, iv.length, baos.size());
// Return
return ivPlusEncryptedBytes;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String decrypt(byte[] ivPlusEncryptedBytes, Optional<String> aad) {
try {
// Get IV
byte iv[] = new byte[IV_SIZE];
System.arraycopy(ivPlusEncryptedBytes, 0, iv, 0, IV_SIZE);
// Initialize GCM Parameters
GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_SIZE, iv);
// Transformation specifies algortihm, mode of operation and padding
Cipher c = Cipher.getInstance(
String.format("%s/%s/%s",ALGORITHM_NAME,MODE_OF_OPERATION,PADDING_SCHEME)
);
// Get encrypted bytes
byte [] encryptedBytes = new byte[ivPlusEncryptedBytes.length - IV_SIZE];
System.arraycopy(ivPlusEncryptedBytes, IV_SIZE, encryptedBytes, 0, encryptedBytes.length);
// Init for decryption
c.init(Cipher.DECRYPT_MODE, secretKey, spec, secureRandom);
// Add AAD tag data if present
aad.ifPresent(t -> {
try {
c.updateAAD(t.getBytes("UTF-8"));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// Add message to decrypt
c.update(encryptedBytes);
// Decrypt
byte[] decryptedBytes
= c.doFinal();
// Return
return new String(decryptedBytes, "UTF-8");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* An abstract method to be implemented by a subclass following the
* Gang of four <a href="https://www.digitalocean.com/community/tutorials/template-method-design-pattern-in-java">Template Method Design Pattern</a>.
* This method is to use the {@link Cipher} provided to return the
* encrypted bytes of the {@link message} parameter.
*
* @param message The String to be encrypted.
* @param cipher The Cipher object used to encrypt the message.
* @return
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws IOException
*/
abstract ByteArrayOutputStream getEncryptedBytes(String message, Cipher cipher) throws BadPaddingException, IllegalBlockSizeException, IOException;
}
</code></pre>
<h1 id="listing7-aesusingsinglepartencryption.javaclass">Listing 7 - AesUsingSinglePartEncryption.java class</h1>
<pre><code class="java">package org.thoth.crypto.symmetric;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class AesUsingSinglePartEncryption extends Aes {
public AesUsingSinglePartEncryption(SecretKey secretKey) {
super(secretKey);
}
/**
* This method demonstrates how to perform a single-part encryption
* operation by using the {@link Cipher#doFinal(byte[]) } method to
* get all of the encrypted bytes.
*
* @param message The message to encrypt.
* @param c The {@link Cipher} used to get the encrypted bytes.
* @return
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws IOException
*/
@Override
ByteArrayOutputStream getEncryptedBytes(String message, Cipher c) throws BadPaddingException, IllegalBlockSizeException, IOException {
// Create output array to hold all the encrypted bytes
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Perform single-part encryption.
baos.write(
c.doFinal(message.getBytes("UTF-8"))
);
return baos;
}
}
</code></pre>
<h1 id="listing8-aesusingmultiplepartencryption.javaclass">Listing 8 - AesUsingMultiplePartEncryption.java class</h1>
<pre><code class="java">package org.thoth.crypto.symmetric;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class AesUsingMultiplePartEncryption extends Aes {
public AesUsingMultiplePartEncryption(SecretKey secretKey) {
super(secretKey);
}
/**
* This method demonstrates how to perform a multiple-part encryption
* operation by using the {@link Cipher#update(byte[]) } and the
* {@link Cipher#doFinal() } methods to get all of the encrypted
* bytes. This method <b>simulates</b> the operation by dividing the
* {@link message} parameter into many strings and operating on those
* strings individually. In reality you would probably never do this, but
* it's an easy way to demonstrate to perform a multiple-part encryption.
*
* @param message The message to encrypt. This method will <b>simulate</b>
* multiple-part encryption by <b>unnecessarily</b> dividing this string into many strings.
* @param c The {@link Cipher} used to get the encrypted bytes.
* @return
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws IOException
*/
@Override
ByteArrayOutputStream getEncryptedBytes(String message, Cipher c) throws BadPaddingException, IllegalBlockSizeException, IOException {
// Create output array to hold all the encrypted bytes
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Chunk the message into many strings to simulate multiple-part encryption
int chunkSize = 5;
String[] chunks = message.split("(?<=\\G.{" + chunkSize + "})");
// Add each chunk to a multiple-part encryption.
// Don't forget to collect the encypted bytes
// along the way!
for (String chunk : chunks) {
baos.write(
c.update(chunk.getBytes("UTF-8"))
);
}
// Finish multi-part encryption. Again,
// Don't forget to collect the encypted bytes
// along the way!
baos.write(
c.doFinal()
);
return baos;
}
}
</code></pre>
<h1 id="summary">Summary</h1>
<p>Encryption isn’t easy. And easy examples will result in implementations with security vulnerabilities for your application. If you need a single key, symmetric, encryption algorithm, use cipher AES/GCM/PKCS5Padding with a 256 bit key and a 96 bit IV.</p>
<h1 id="references">References</h1>
<p><em>Java Cryptography Extension (JCE) Unlimited Strength</em>. (n.d.). Retrieved from <a href="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html">http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html</a>.</p>
<p>Sheth, M. (2017, April 18). <em>Encryption and Decryption in Java Cryptography</em>. Retrieved from <a href="https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography">https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography</a>.</p>
<p>cpast[ Says GCM IV is 96bit which is 96/8 = 12 bytes]. (2015, June 4). <em>Encrypting using AES-256, can I use 256 bits IV</em> [Web log comment]. Retrieved from <a href="https://security.stackexchange.com/questions/90848/encrypting-using-aes-256-can-i-use-256-bits-iv">https://security.stackexchange.com/questions/90848/encrypting-using-aes-256-can-i-use-256-bits-iv</a>.</p>
<p>Bodewes[ Says GCM IV is strongly recommended to be 12 bytes (12*8 = 96) but can be of any size. Other sizes will require additional calculations], M. (2015, July 7). <em>Ciphertext and tag size and IV transmission with AES in GCM mode</em> [Web log comment]. Retrieved from <a href="https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode">https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode</a>.</p>
<p>Figlesquidge. (2013, October 18). <em>What’s the difference between a ‘cipher’ and a ‘mode of operation’?</em> [Web log comment]. Retrieved from <a href="https://crypto.stackexchange.com/questions/11132/what-is-the-difference-between-a-cipher-and-a-mode-of-operation">https://crypto.stackexchange.com/questions/11132/what-is-the-difference-between-a-cipher-and-a-mode-of-operation</a>.</p>
<p>Toust, S. (2013, February 4). <em>Why does the recommended key size between symmetric and asymmetric encryption differ greatly?</em>. Retrieved from <a href="https://crypto.stackexchange.com/questions/6236/why-does-the-recommended-key-size-between-symmetric-and-asymmetric-encryption-di">https://crypto.stackexchange.com/questions/6236/why-does-the-recommended-key-size-between-symmetric-and-asymmetric-encryption-di</a>.</p>
<p>Karonen, I. (2012, October 5). <em>What is the main difference between a key, an IV and a nonce?</em>. Retrieved from <a href="https://crypto.stackexchange.com/questions/3965/what-is-the-main-difference-between-a-key-an-iv-and-a-nonce">https://crypto.stackexchange.com/questions/3965/what-is-the-main-difference-between-a-key-an-iv-and-a-nonce</a>.</p>
<p><em>Block cipher mode of operation</em>. (2017, November 6). Wikipedia. Retrieved from <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Initialization_vector_.28IV.29">https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Initialization_vector_.28IV.29</a></p>
<p>Jakobsen, Peter. (2022, March 10). <em>javax.crypto.AEADBadTagException: Tag mismatch! when password.length >= 16 - Stack Overflow</em> [Web log post]. Retrieved from <a href="https://stackoverflow.com/questions/71422498/javax-crypto-aeadbadtagexception-tag-mismatch-when-password-length-16">https://stackoverflow.com/questions/71422498/javax-crypto-aeadbadtagexception-tag-mismatch-when-password-length-16</a>.</p>mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-56015866029514614122017-12-20T12:32:00.001-06:002017-12-22T08:22:05.074-06:00Choosing Java Cryptographic Algorithms Part 1 - Hashing<h1 id="abstract">Abstract</h1>
<p>This is the 1st of a three-part blog series covering Java cryptographic algorithms. The series covers how to implement the following:</p>
<ol>
<li><a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_20.html" title="Hashing with SHA-512">Hashing with <strong>SHA–512</strong></a></li>
<li><a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_22.html" title="Single-key symmetric encryption with AES-256">Single-key symmetric encryption with <strong>AES–256</strong></a></li>
<li><a href="http://mjremijan.blogspot.com/2017/12/choosing-java-cryptographic-algorithms_5.html" title="Public/Private key asymmetric encryption with RSA-4096">Public/Private key asymmetric encryption with <strong>RSA–4096</strong></a></li>
</ol>
<p>This 1st post details how to implement SHA–512 hashing. Let’s get started.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="requirements">Requirements</h1>
<p>I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.</p>
<ul>
<li>Java 1.8.0_152_x64</li>
<li>NetBeans 8.2 (Build 201609300101)</li>
<li>Maven 3.0.5 (Bundled with NetBeans)</li>
</ul>
<h1 id="download">Download</h1>
<p>Visit <a href="https://github.com/mjremijan" title="My Github page link">my GitHub Page</a> to see all of my open source projects. The code for this post is located in project: <a href="https://github.com/mjremijan/thoth-cryptography" title="thoth-cryptography project link">thoth-cryptography</a></p>
<h1 id="hashing">Hashing</h1>
<h2 id="about">About</h2>
<p>Hashing is a one-way cryptographic algorithm which takes in a message of any length and outputs a repeatable, fixed-length, and one-way digest (hash) of the message. Being one-way, it’s supposed to be impossible to regenerate the original message from the hash. Identical messages will always generate the same hash. </p>
<p>A hash can be used to authenticate an original message. A common use of hashing is validating passwords. Instead of storing the password itself, the hash of the password is stored. To verify a password, the stored hash is compared with a new hash of an incoming password during a login process.</p>
<p>Because identical messages generate the same hash, a salt value is used to make the hash more secure (Salt, 2017, para. 1). Consider a case where the same password is used by multiple users. A salt value combined with the original password allows for unique hash values. This is important because if the hashed values are ever compromised, identical hashes let a hacker know those passwords are the same. </p>
<h2 id="sha-512">SHA–512</h2>
<p>Research done as of today seems to indicate the best and most secure algorithm for hashing is SHA–512, which uses 64-bit words (Secure Hash Algorithms, 2017, para.2). Let’s take a look at an example.</p>
<blockquote>
<p><strong>NOTE</strong>
Don’t use MD5 as a secure hash. It has many vulnerabilities (MD5, 2017, para. 1). Limit MD5 use to checksums and data verification.</p>
</blockquote>
<h1 id="example">Example</h1>
<p>Listing 1 is the <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-02-hash-sha512/src/test/java/org/thoth/security/hash/ShaTest.java" title="ShaTest.java">ShaTest.java</a> unit test demonstrating how to hash. Listing 2 is the <a href="https://github.com/mjremijan/thoth-cryptography/blob/master/thoth-cryptography-02-hash-sha512/src/main/java/org/thoth/security/hash/Sha.java" title="Sha.java">Sha.java</a> class which does the hash.</p>
<h1 id="listing1-shatest.javaclass">Listing 1 - ShaTest.java class</h1>
<pre><code class="java">package org.thoth.security.hash;
import java.util.Optional;
import org.junit.Assert;
import org.junit.Test;
/**
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class ShaTest {
@Test
public void test_hash_with_optional_to_hex() throws Exception {
// setup
String username = "mjremijan";
String password = "super!secret";
Sha sha = new Sha();
// test
String asHex
= sha.hashToHex(password, Optional.of(username));
// assert
Assert.assertEquals(
"F38CD5290D11B20159E36740843A8D93CFDFA395CF594F328613EF5C7BA42D9EAC00BF3EE47B7E8CE1587040B36365F05C8E15E9392C288A1D7C4CFB66097848"
, asHex);
}
@Test
public void test_hash_without_optional_to_hex() throws Exception {
// setup
String password = "super!secret";
Sha sha = new Sha();
// test
String asHex
= sha.hashToHex(password, Optional.empty());
// assert
Assert.assertEquals(
"516A1FE9D87FE5B953D91B48B1A2FFA5AE5F670914C1B6FE0835D8877918DC4E8BC8FB8CCD520DBA940C21B4F294DFD1B4EFF2E06AB110C6A06E35068251C1DD"
, asHex);
}
@Test
public void test_hash_with_optional_to_base64() throws Exception {
// setup
String username = "mjremijan";
String password = "super!secret";
Sha sha = new Sha();
// test
String asBase64
= sha.hashToBase64(password, Optional.of(username));
// assert
Assert.assertEquals(
"84ZVKQ0RSGFZ42DAHDQNK8/FO5XPWU8YHHPVXHUKLZ6SAL8+5HT+JOFYCECZY2XWXI4V6TKSKIODFEZ7ZGL4SA=="
, asBase64);
}
@Test
public void test_hash_without_optional_to_base64() throws Exception {
// setup
String password = "super!secret";
Sha sha = new Sha();
// test
String asBase64
= sha.hashToBase64(password, Optional.empty());
// assert
Assert.assertEquals(
"UWOF6DH/5BLT2RTISAL/PA5FZWKUWBB+CDXYH3KY3E6LYPUMZVINUPQMIBTYLN/RTO/Y4GQXEMAGBJUGGLHB3Q=="
, asBase64);
}
}
</code></pre>
<h1 id="listing2-sha.javaclass">Listing 2 - Sha.java class</h1>
<pre><code class="java">package org.thoth.security.hash;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Optional;
/**
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class Sha {
public String hashToHex(String hashMe, Optional<String> salt)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
byte[] bytes
= hash(hashMe, salt);
StringBuilder sp
= new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sp.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
return sp.toString().toUpperCase();
}
public String hashToBase64(String hashMe, Optional<String> salt)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
return Base64.getEncoder().encodeToString(
hash(hashMe, salt)
).toUpperCase();
}
public byte[] hash(String hashMe, Optional<String> salt)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md
= MessageDigest.getInstance("SHA-512");
md.update(hashMe.getBytes("UTF-8"));
salt.ifPresent(s -> {
try { md.update(s.getBytes("UTF-8")); } catch (Exception e) {throw new RuntimeException(e);}
});
return md.digest();
}
}
</code></pre>
<h1 id="summary">Summary</h1>
<p>Hashing is pretty easy. Choose a strong hashing algorithm like SHA–512 for securing your application data. Avoid MD5 for securing data. Stay current as to which algorithms are strong and safe. Update your application if you are using an older algorithm which has vulnerabilities or is compromised.</p>
<h1 id="references">References</h1>
<p>Salt (cryptography). (2017, November 3). Wikipedia. Retrieved from <a href="https://en.wikipedia.org/wiki/Salt_(cryptography)">https://en.wikipedia.org/wiki/Salt_(cryptography)</a>.</p>
<p>Secure Hash Algorithms. (2017, November 25). Wikipedia. Retrieved from <a href="https://en.wikipedia.org/wiki/Secure_Hash_Algorithms">https://en.wikipedia.org/wiki/Secure_Hash_Algorithms</a>.</p>
<p>MD5. (2017, November 22). Wikipedia. Retrieved from <a href="https://en.wikipedia.org/wiki/MD5">https://en.wikipedia.org/wiki/MD5</a>.</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0tag:blogger.com,1999:blog-383035171837618940.post-81618782153673731052017-10-13T06:49:00.000-05:002017-10-13T06:49:55.255-05:00Double click a Windows batch file (*.bat, *.cmd) and keep the command prompt open<h1 id="abstract">Abstract</h1>
<p>This is a quick tip about Windows batch files (<em>.bat, </em>.cmd). I often use these files to setup different development environments; setting environment variables, update <code>%PATH%</code>, different <code>%JAVA_HOME%</code>, etc. Sometimes it can be a challenge to do this, not lose these settings, and keep the command prompt (DOS window) open all at the same time. This is a technique I found which seems to do all this reliably. </p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.</p>
<h1 id="example">Example</h1>
<p>This is an example <code>run.cmd</code> file. The basic idea is when you double-click this file, the script will actually call itself again which you can see in the <code>:cmd</code> section. After the script calls itself again, the <code>:run</code> section is where you put the details of your script.</p>
<h1 id="listing1-run.cmd">Listing 1 - run.cmd</h1>
<pre><code class="bat">@echo off
REM Check the value of the 1st command line argument.
REM If the value is empty, go to the 'cmd' section
IF "%~1" == "" GOTO cmd
REM Check the value of the 1st command line argument.
REM If the value is "run", go to the 'run' section
IF "%~1" == "run" GOTO run
REM If the 1st command line argument is not empty,
REM but not the value "run" then just 'end' because
REM this script doesn't know how to handle it.
ECHO Command line argument "%1" is not understood.
GOTO end
:cmd
REM In this section, use the %0 value in order to
REM have this script call itself again. By calling
REM itself with `cmd /K` you get a command prompt
REM (DOS window) which won't automatically close
REM when this script is finished executing.
ECHO In the 'cmd' section
ECHO Script file=%0
cmd /K "%0 run"
GOTO end
:run
REM In this section, this is where you want to put
REM the work of your script. You can execute whatever
REM you need and even call other scripts. The
REM command prompt window will remain open because
REM of what the 'cmd' section does.
ECHO In the 'run' section
SET SOME_PROPERTY=foo
ECHO SOME_PROPERTY=%SOME_PROPERTY%
ECHO call env.cmd
call "%~dp0\env.cmd"
ECHO ENV_PROPERTY_1=%ENV_PROPERTY_1%
ECHO ENV_PROPERTY_2=%ENV_PROPERTY_2%
ECHO ENV_PROPERTY_3=%ENV_PROPERTY_3%
:end
</code></pre>
<h1 id="summary">Summary</h1>
<p>That’s it. Really easy. Enjoy!</p>
mjremijanhttp://www.blogger.com/profile/01721904127841880944noreply@blogger.com0