-
Notifications
You must be signed in to change notification settings - Fork 20.5k
To add Implementation of Base64 Algorithm #6586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
alxkm
merged 9 commits into
TheAlgorithms:master
from
NithinU2802:nithinu/base64-algorithm
Oct 2, 2025
Merged
Changes from 5 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
bb6acd3
feat: base64 algorithm implementation
NithinU2802 449961b
feat: test coverage implementation of Base64Test.java
NithinU2802 3011883
Update DIRECTORY.md
NithinU2802 30ed79a
fix: build and lint fix update
NithinU2802 c75a673
patch: linter fix
NithinU2802 1104246
fix: enforce strict RFC 4648 compliance in Base64 decoding
NithinU2802 c902212
fix: enforce strict rfc 4648 compliance in base64 decoding
NithinU2802 c40a195
Merge branch 'nithinu/base64-algorithm' of https://github.com/NithinU…
NithinU2802 7c01f73
Merge branch 'master' into nithinu/base64-algorithm
alxkm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
194 changes: 194 additions & 0 deletions
194
src/main/java/com/thealgorithms/conversions/Base64.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
package com.thealgorithms.conversions; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
/** | ||
* Base64 is a group of binary-to-text encoding schemes that represent binary data | ||
* in an ASCII string format by translating it into a radix-64 representation. | ||
* Each base64 digit represents exactly 6 bits of data. | ||
* | ||
* Base64 encoding is commonly used when there is a need to encode binary data | ||
* that needs to be stored and transferred over media that are designed to deal | ||
* with textual data. | ||
* | ||
* Wikipedia Reference: https://en.wikipedia.org/wiki/Base64 | ||
* Author: Nithin U. | ||
* Github: https://github.com/NithinU2802 | ||
*/ | ||
|
||
public final class Base64 { | ||
|
||
// Base64 character set | ||
private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||
private static final char PADDING_CHAR = '='; | ||
|
||
private Base64() { | ||
} | ||
|
||
/** | ||
* Encodes the given byte array to a Base64 encoded string. | ||
* | ||
* @param input the byte array to encode | ||
* @return the Base64 encoded string | ||
* @throws IllegalArgumentException if input is null | ||
*/ | ||
public static String encode(byte[] input) { | ||
if (input == null) { | ||
throw new IllegalArgumentException("Input cannot be null"); | ||
} | ||
|
||
if (input.length == 0) { | ||
return ""; | ||
} | ||
|
||
StringBuilder result = new StringBuilder(); | ||
int padding = 0; | ||
|
||
// Process input in groups of 3 bytes | ||
for (int i = 0; i < input.length; i += 3) { | ||
// Get up to 3 bytes | ||
int byte1 = input[i] & 0xFF; | ||
int byte2 = (i + 1 < input.length) ? (input[i + 1] & 0xFF) : 0; | ||
int byte3 = (i + 2 < input.length) ? (input[i + 2] & 0xFF) : 0; | ||
|
||
// Calculate padding needed | ||
if (i + 1 >= input.length) { | ||
padding = 2; | ||
} else if (i + 2 >= input.length) { | ||
padding = 1; | ||
} | ||
|
||
// Combine 3 bytes into a 24-bit number | ||
int combined = (byte1 << 16) | (byte2 << 8) | byte3; | ||
|
||
// Extract four 6-bit groups | ||
result.append(BASE64_CHARS.charAt((combined >> 18) & 0x3F)); | ||
result.append(BASE64_CHARS.charAt((combined >> 12) & 0x3F)); | ||
result.append(BASE64_CHARS.charAt((combined >> 6) & 0x3F)); | ||
result.append(BASE64_CHARS.charAt(combined & 0x3F)); | ||
} | ||
|
||
// Replace padding characters | ||
if (padding > 0) { | ||
result.setLength(result.length() - padding); | ||
for (int i = 0; i < padding; i++) { | ||
result.append(PADDING_CHAR); | ||
} | ||
} | ||
|
||
return result.toString(); | ||
} | ||
|
||
/** | ||
* Encodes the given string to a Base64 encoded string using UTF-8 encoding. | ||
* | ||
* @param input the string to encode | ||
* @return the Base64 encoded string | ||
* @throws IllegalArgumentException if input is null | ||
*/ | ||
public static String encode(String input) { | ||
if (input == null) { | ||
throw new IllegalArgumentException("Input cannot be null"); | ||
} | ||
|
||
return encode(input.getBytes(StandardCharsets.UTF_8)); | ||
} | ||
|
||
/** | ||
* Decodes the given Base64 encoded string to a byte array. | ||
* | ||
* @param input the Base64 encoded string to decode | ||
* @return the decoded byte array | ||
* @throws IllegalArgumentException if input is null or contains invalid Base64 characters | ||
*/ | ||
public static byte[] decode(String input) { | ||
if (input == null) { | ||
throw new IllegalArgumentException("Input cannot be null"); | ||
} | ||
|
||
if (input.isEmpty()) { | ||
return new byte[0]; | ||
} | ||
|
||
// Remove padding for processing | ||
String cleanInput = input.replace("=", ""); | ||
int padding = input.length() - cleanInput.length(); | ||
|
||
// Validate input length | ||
if ((cleanInput.length() % 4) + padding > 4) { | ||
throw new IllegalArgumentException("Invalid Base64 input length"); | ||
} | ||
|
||
List<Byte> result = new ArrayList<>(); | ||
|
||
// Process input in groups of 4 characters | ||
for (int i = 0; i < cleanInput.length(); i += 4) { | ||
// Get up to 4 characters | ||
int char1 = getBase64Value(cleanInput.charAt(i)); | ||
int char2 = (i + 1 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 1)) : 0; | ||
int char3 = (i + 2 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 2)) : 0; | ||
int char4 = (i + 3 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 3)) : 0; | ||
|
||
// Combine four 6-bit groups into a 24-bit number | ||
int combined = (char1 << 18) | (char2 << 12) | (char3 << 6) | char4; | ||
|
||
// Extract three 8-bit bytes | ||
result.add((byte) ((combined >> 16) & 0xFF)); | ||
if (i + 2 < cleanInput.length() || (i + 2 == cleanInput.length() && padding < 2)) { | ||
result.add((byte) ((combined >> 8) & 0xFF)); | ||
} | ||
if (i + 3 < cleanInput.length() || (i + 3 == cleanInput.length() && padding < 1)) { | ||
result.add((byte) (combined & 0xFF)); | ||
} | ||
} | ||
|
||
// Convert List<Byte> to byte[] | ||
byte[] resultArray = new byte[result.size()]; | ||
for (int i = 0; i < result.size(); i++) { | ||
resultArray[i] = result.get(i); | ||
} | ||
|
||
return resultArray; | ||
} | ||
|
||
/** | ||
* Decodes the given Base64 encoded string to a string using UTF-8 encoding. | ||
* | ||
* @param input the Base64 encoded string to decode | ||
* @return the decoded string | ||
* @throws IllegalArgumentException if input is null or contains invalid Base64 characters | ||
*/ | ||
public static String decodeToString(String input) { | ||
if (input == null) { | ||
throw new IllegalArgumentException("Input cannot be null"); | ||
} | ||
|
||
byte[] decodedBytes = decode(input); | ||
return new String(decodedBytes, StandardCharsets.UTF_8); | ||
} | ||
|
||
/** | ||
* Gets the numeric value of a Base64 character. | ||
* | ||
* @param c the Base64 character | ||
* @return the numeric value (0-63) | ||
* @throws IllegalArgumentException if character is not a valid Base64 character | ||
*/ | ||
private static int getBase64Value(char c) { | ||
if (c >= 'A' && c <= 'Z') { | ||
return c - 'A'; | ||
} else if (c >= 'a' && c <= 'z') { | ||
return c - 'a' + 26; | ||
} else if (c >= '0' && c <= '9') { | ||
return c - '0' + 52; | ||
} else if (c == '+') { | ||
return 62; | ||
} else if (c == '/') { | ||
return 63; | ||
} else { | ||
throw new IllegalArgumentException("Invalid Base64 character: " + c); | ||
} | ||
} | ||
} |
167 changes: 167 additions & 0 deletions
167
src/test/java/com/thealgorithms/conversions/Base64Test.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package com.thealgorithms.conversions; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertArrayEquals; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.CsvSource; | ||
|
||
/** | ||
* Test cases for Base64 encoding and decoding. | ||
* | ||
* Author: Nithin U. | ||
* Github: https://github.com/NithinU2802 | ||
*/ | ||
|
||
class Base64Test { | ||
|
||
@Test | ||
void testBase64Alphabet() { | ||
// Test that all Base64 characters are handled correctly | ||
String allChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||
String encoded = Base64.encode(allChars); | ||
String decoded = Base64.decodeToString(encoded); | ||
assertEquals(allChars, decoded); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({"'', ''", "A, QQ==", "AB, QUI=", "ABC, QUJD", "ABCD, QUJDRA==", "Hello, SGVsbG8=", "'Hello World', SGVsbG8gV29ybGQ=", "'Hello, World!', 'SGVsbG8sIFdvcmxkIQ=='", "'The quick brown fox jumps over the lazy dog', 'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=='", | ||
"123456789, MTIzNDU2Nzg5", "'Base64 encoding test', 'QmFzZTY0IGVuY29kaW5nIHRlc3Q='"}) | ||
void | ||
testStringEncoding(String input, String expected) { | ||
assertEquals(expected, Base64.encode(input)); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({"'', ''", "QQ==, A", "QUI=, AB", "QUJD, ABC", "QUJDRA==, ABCD", "SGVsbG8=, Hello", "'SGVsbG8gV29ybGQ=', 'Hello World'", "'SGVsbG8sIFdvcmxkIQ==', 'Hello, World!'", "'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==', 'The quick brown fox jumps over the lazy dog'", | ||
"MTIzNDU2Nzg5, 123456789", "'QmFzZTY0IGVuY29kaW5nIHRlc3Q=', 'Base64 encoding test'"}) | ||
void | ||
testStringDecoding(String input, String expected) { | ||
assertEquals(expected, Base64.decodeToString(input)); | ||
} | ||
|
||
@Test | ||
void testByteArrayEncoding() { | ||
byte[] input = {72, 101, 108, 108, 111}; | ||
String expected = "SGVsbG8="; | ||
assertEquals(expected, Base64.encode(input)); | ||
} | ||
|
||
@Test | ||
void testByteArrayDecoding() { | ||
String input = "SGVsbG8="; | ||
byte[] expected = {72, 101, 108, 108, 111}; | ||
assertArrayEquals(expected, Base64.decode(input)); | ||
} | ||
|
||
@Test | ||
void testRoundTripEncoding() { | ||
String[] testStrings = {"", "A", "AB", "ABC", "Hello, World!", "The quick brown fox jumps over the lazy dog", "1234567890", "Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?", | ||
"Unicode: வணக்கம்", // Tamil for "Hello" | ||
"Multi-line\nstring\rwith\tdifferent\nwhitespace"}; | ||
|
||
for (String original : testStrings) { | ||
String encoded = Base64.encode(original); | ||
String decoded = Base64.decodeToString(encoded); | ||
assertEquals(original, decoded, "Round trip failed for: " + original); | ||
} | ||
} | ||
|
||
@Test | ||
void testRoundTripByteArrayEncoding() { | ||
byte[][] testArrays = {{}, {0}, {-1}, {0, 1, 2, 3, 4, 5}, {-128, -1, 0, 1, 127}, {72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}}; | ||
|
||
for (byte[] original : testArrays) { | ||
String encoded = Base64.encode(original); | ||
byte[] decoded = Base64.decode(encoded); | ||
assertArrayEquals(original, decoded, "Round trip failed for byte array"); | ||
} | ||
} | ||
|
||
@Test | ||
void testBinaryData() { | ||
// Test with binary data that might contain null bytes | ||
byte[] binaryData = new byte[256]; | ||
for (int i = 0; i < 256; i++) { | ||
binaryData[i] = (byte) i; | ||
} | ||
|
||
String encoded = Base64.encode(binaryData); | ||
byte[] decoded = Base64.decode(encoded); | ||
assertArrayEquals(binaryData, decoded); | ||
} | ||
|
||
@Test | ||
void testNullInputEncoding() { | ||
assertThrows(IllegalArgumentException.class, () -> Base64.encode((String) null)); | ||
assertThrows(IllegalArgumentException.class, () -> Base64.encode((byte[]) null)); | ||
} | ||
|
||
@Test | ||
void testNullInputDecoding() { | ||
assertThrows(IllegalArgumentException.class, () -> Base64.decode(null)); | ||
assertThrows(IllegalArgumentException.class, () -> Base64.decodeToString(null)); | ||
} | ||
|
||
@Test | ||
void testInvalidBase64Characters() { | ||
assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8@")); | ||
assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8#")); | ||
assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8$")); | ||
assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8%")); | ||
} | ||
|
||
@Test | ||
void testPaddingVariations() { | ||
// Test different padding scenarios '=' | ||
assertEquals("A", Base64.decodeToString("QQ==")); | ||
assertEquals("AB", Base64.decodeToString("QUI=")); | ||
assertEquals("ABC", Base64.decodeToString("QUJD")); | ||
} | ||
|
||
@Test | ||
void testPaddingConsistency() { | ||
// Ensure that strings requiring different amounts of padding encode/decode correctly | ||
String[] testCases = {"A", "AB", "ABC", "ABCD", "ABCDE", "ABCDEF"}; | ||
|
||
for (String test : testCases) { | ||
String encoded = Base64.encode(test); | ||
String decoded = Base64.decodeToString(encoded); | ||
assertEquals(test, decoded); | ||
|
||
// Verify padding is correct | ||
int expectedPadding = (3 - (test.length() % 3)) % 3; | ||
int actualPadding = 0; | ||
for (int i = encoded.length() - 1; i >= 0 && encoded.charAt(i) == '='; i--) { | ||
actualPadding++; | ||
} | ||
assertEquals(expectedPadding, actualPadding, "Incorrect padding for: " + test); | ||
} | ||
} | ||
|
||
@Test | ||
void testLargeData() { | ||
// Test with larger data to ensure scalability | ||
StringBuilder largeString = new StringBuilder(); | ||
for (int i = 0; i < 1000; i++) { | ||
largeString.append("This is a test string for Base64 encoding. "); | ||
} | ||
|
||
String original = largeString.toString(); | ||
String encoded = Base64.encode(original); | ||
String decoded = Base64.decodeToString(encoded); | ||
assertEquals(original, decoded); | ||
} | ||
|
||
@Test | ||
void testEmptyAndSingleCharacter() { | ||
// Test edge cases | ||
assertEquals("", Base64.encode("")); | ||
assertEquals("", Base64.decodeToString("")); | ||
|
||
assertEquals("QQ==", Base64.encode("A")); | ||
assertEquals("A", Base64.decodeToString("QQ==")); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.