A Dart package for encoding and decoding arbitrary data into emojis using Unicode variation selectors. Hide text within any Unicode character invisibly.
Based on the method specified in this article by Paul Butler and written to make this emoji-encoder
implementation.
This technique abuses the Unicode specification and should not be used in production systems. It can bypass visual content filters and has potential for malicious use. This package is provided for educational and research purposes only.
- 🎭 Invisible Encoding: Hide text messages within any Unicode character.
- 🔍 Multi-message Support: Encode multiple messages in a single text string.
- 🌐 Unicode Compatible: Full support for Unicode characters including emojis.
- 📊 Text Analysis: Get statistics about encoded text.
- 🔧 Detection Tools: Check if text contains hidden data.
- 📋 Copy-paste Safe: Hidden data survives copy/paste operations.
Add this to your package's pubspec.yaml
file:
dependencies:
emoji_transcoder: ^0.0.1
Then run:
dart pub get
import 'package:emoji_transcoder/emoji_transcoder.dart';
void main() {
// Encode a message
final encoded = EmojiTranscoder.encode('😊', 'Hello, World!');
print(encoded); // Looks like just '😊' but contains hidden data
// Decode the message
final decoded = EmojiTranscoder.decode(encoded);
print(decoded); // 'Hello, World!'
}
// Encode multiple messages
final multipleMessages = {
'😊': 'Hello',
'🌟': 'World',
'🎯': 'Secret',
};
final encoded = EmojiTranscoder.encodeMultiple(multipleMessages);
print(encoded); // Looks like: '😊🌟🎯'
// Decode all messages
final messages = EmojiTranscoder.decodeAll(encoded);
for (final msg in messages) {
print('${msg.baseCharacter}: ${msg.message}');
}
// Output:
// 😊: Hello
// 🌟: World
// 🎯: Secret
final text = EmojiTranscoder.encode('🔐', 'secret data');
// Check if text contains hidden data.
bool hasHidden = EmojiTranscoder.hasHiddenData(text);
print('Has hidden data: $hasHidden'); // true
// Get visible text only.
String visible = EmojiTranscoder.getVisibleText(text);
print('Visible: $visible'); // '🔐'.
// Get detailed statistics.
Map<String, int> stats = EmojiTranscoder.getStats(text);
print('Total length: ${stats['totalLength']}');
print('Visible length: ${stats['visibleLength']}');
print('Hidden bytes: ${stats['hiddenBytes']}');
print('Message count: ${stats['messageCount']}');
// Works with any Unicode characters.
final unicodeMessage = 'Héllo 世界! 🚀✨';
final encoded = EmojiTranscoder.encode('📝', unicodeMessage);
final decoded = EmojiTranscoder.decode(encoded);
print(unicodeMessage == decoded); // true.
encode(String baseCharacter, String message)
- Encode a message into a base character.decode(String encodedText)
- Decode the first hidden message.decodeAll(String encodedText)
- Decode all hidden messages.encodeMultiple(Map<String, String> messages)
- Encode multiple messages.encodeWithDefault(String message, {String? baseCharacter})
- Encode with default base character.hasHiddenData(String text)
- Check if text contains encoded data.getVisibleText(String encodedText)
- Extract visible characters only.getStats(String encodedText)
- Get text statistics.
Represents a decoded message with its base character:
class DecodedMessage {
final String baseCharacter;
final String message;
}
EncodingException
- Thrown when encoding fails.DecodingException
- Thrown when decoding fails.InvalidByteException
- Thrown for invalid byte values.InvalidVariationSelectorException
- Thrown for invalid variation selectors.
This package uses Unicode variation selectors (U+FE00 to U+FE0F) to encode binary data. Each byte of the input message is mapped to a specific variation selector codepoint, which is then appended to the base character. These variation selectors are invisible when rendered but preserved during text operations.
The encoding process:
- Convert message text to UTF-8 bytes.
- Map each byte to a variation selector codepoint.
- Append variation selectors to the base character.
- Add a null terminator (U+FE0F).
The decoding process reverses this by extracting variation selectors and converting them back to the original message.
See the example/
directory for complete usage examples:
example/emoji_transcoder_example.dart
- Basic library usage examplesexample/emoji_cli/
- Full-featured CLI application for clipboard-based encoding/decoding
The package includes a command-line interface for practical use:
cd example/emoji_cli
dart pub get
dart run bin/main.dart --help
Features:
- Command-line mode:
emoji_cli --encode "😊:Hello World"
- Interactive mode: Run without arguments for a persistent session
- Clipboard integration: Direct read/write to system clipboard
- Multiple encoding methods: Standard and safe ZWJ encoding
- Analysis tools: Statistics, detection, and text inspection
- Batch operations: Encode/decode multiple messages at once
Example usage:
# Encode a secret message
emoji_cli --encode "🔐:This is secret"
# Check what's in clipboard (appears as just 🔐)
emoji_cli --raw
# Decode the hidden message
emoji_cli --decode
# Output: This is secret
# Interactive mode for multiple operations
emoji_cli
Note: Use plain Unicode characters as base (🐈 not 🐈️). Avoid emojis with variation selectors.
Run the test suite:
dart test
- Fork the repository.
- Create a feature branch.
- Make your changes.
- Add tests for new functionality.
- Run tests and ensure they pass.
- Submit a pull request.
This project is licensed under the MIT License - see the LICENSE file for details.