paragonie / halite Goto Github PK
View Code? Open in Web Editor NEWHigh-level cryptography interface powered by libsodium
Home Page: https://paragonie.com/project/halite
License: Mozilla Public License 2.0
High-level cryptography interface powered by libsodium
Home Page: https://paragonie.com/project/halite
License: Mozilla Public License 2.0
HiddenString
container instead of exposing passwords to stack tracesfinal
where abstract
is used nowI tried to run the unit tests and got this error:
[firexware@firexware-pc halite]$ ./test/phpunit.sh
gpg: error reading key: No public key
Downloading PGP Public Key...
gpg: keyserver receive failed: No keyserver available
gpg: error reading key: No public key
Could not download PGP public key for verification
Maybe a keyserver should be specified explicitly in the script?
PHP 7.2 is behind the doors and many of us will switch to sodium as our encryption library. It is not easy to use so we will look for a higher level interface. Halite is currently only one doing this job and it is doing it really well. But is unusable for majority of projects because of license.
That majority cannot use GPLv3 licensed libraries. Please, give us choice to use another license by default instead of offering commercial licenses. We cannot rely on just a chance of obtaining non-gpl license.
We need to know there is a encryption library we can use everytime with all our projects. And if halite is not library which allow it, community will create alternatives. Why we should do it when perfect library currently exists?
Code exerpt:
if ($exists) {
$length = \mb_strlen($str, '8bit');
if ($length === false) {
}
return $length;
PHPUnit 5.0.5 by Sebastian Bergmann and contributors.
Cannot open file "/home/firexware/egits/halite/vendor/autoload.php".
You should look into adding code coverage.
Hi,
I've followed this guide to install the php extension: https://paragonie.com/book/pecl-libsodium/read/00-intro.md#installing-extension
This installed an extension called sodium, not libsodium (ref: https://github.com/jedisct1/libsodium-php)
While when trying to add paragonie/halite via composer, the command failed with an error:
" paragonie/halite dev-master requires ext-libsodium ^1.0.6 -> the requested PHP extension libsodium is missing from your system."
Checking the composer.json file in master reveals that the old libsodium (v1.x) is still required, and installing the latest "sodium" lib doesn't work.
"require": {
"php": "^7",
"paragonie/constant_time_encoding": "^2",
"ext-libsodium": "^1.0.6"
},
note: This might be linked to an existing issue: #51
I would like that HiddenString
cannot be revealed via serialize(...)
; this can be achieved by the __sleep()
magic method returning an empty array. Would this fit within the scope of what HiddenString
sets out to achieve?
Documentation mentions the following
https://github.com/paragonie/halite#example-generating-a-key-from-a-password
Relevant Excerpt:
$salt = "\xdd\x7b\x1e\x38\x75\x9f\x72\x86\x0a\xe9\xc8\x58\xf6\x16\x0d\x3b";
$encryptionKey = KeyFactory::deriveEncryptionKey($passwd, $salt);
In Halite1 the salt needs to be binary format not a hex encoded string. Or else you will get the exception:
salt should be CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES bytes
For version 2 and 3:
Update KeyFactory
to support loading/saving keys to an encoded/serialized string format. We'll probably use the exact same structure as saving to a file.
In version 2: work with strings.
In version 3: work with HiddenString
objects.
This will be released as 2.2.0 and 3.1.0, respectively.
Version 1.0.0 Roadmap:
Key
Asymmetric\SecretKey
Asymmetric\PublicKey
KeyPair
Symmetric\SecretKey
https://github.com/paragonie/halite/blob/master/src/Password.php
Switch from crypto_pwhash_scryptsalsa208sha256*
to crypto_pwhash*
.
Depends on:
In version 4, we should make it easy to tie encrypted password hashes to specific user accounts. A simple win is to change from an AE mode to an AEAD mode (i.e. XChaCha20-Poly1305), and allow the additional data to be passed to the interface.
ParagonIE\Halite\Symmetric\Crypto
would gain an AEAD interface.ParagonIE\Halite\Password
would gain an optional extra string argument to both hash()
and verify()
.---IGNORE THIS COMMENT---
This isn't an issue, but an earlier discussion said to ask for help here, so:
I'm trying to run a pretty simple Halite example (shown below).
<?php
require_once 'vendor/autoload.php';
use ParagonIE\Halite\HiddenString;
use ParagonIE\Halite\KeyFactory;
use ParagonIE\Halite\Symmetric\Crypto as Symmetric;
$encryptionKey = KeyFactory::loadEncryptionKey('/var/key/9.key');
$message = new HiddenString('This is a confidential message for your eyes only.');
$ciphertext = Symmetric::encrypt($message, $encryptionKey);
$decrypted = Symmetric::decrypt($ciphertext, $encryptionKey);
var_dump($decrypted === $message); // bool(true)
?>
However, the program always breaks at new HiddenString();
, for a reason that I can't seem to understand. If anyone has any insight, or can offer any help, that would be most appreciated. Thank you so much!
Debian Jessie
Raspberry Pi Model B
PHP 5.6.24-0+deb8u1
Halite: v1.5.1
PECL extention: v1.0.6
Composer: v1.2.0
Thanks!
Add a Merkle tree implementation that uses the BLAKE2b hash function.
Specifically:
When moving towards PHP 7.2 and upgrading from \Sodium::CONSTANT
towards SODIUM_CONSTANT
, I was noticing that the password hashing mechanism was changed from Argon2i to Argon2id. At first I thought that might be just my configuration, but I also see it on 3v4l.
If this is not configuration, it means that Halite is always rehashing passwords in PHP7.2, because it counts on Argon2i. Or am I just missing something?
Version 2 uses strict typing. This led to a lot of code deletion and API simplification. The diffs:
Before I proceed to tag version 2.0.0, I'd like to be sure of a few things:
MerkleTree
BlockChain
I'll probably end up answering most of these questions myself, but if anyone would like to weigh in, please feel free.
Note: The unit tests are currently failing in master
because I wrote these changes against dev-master
of both libsodium and libsodium-php. As soon as the new release comes out, the tests will pass (I hope!).
The wiki section is not getting used.
The API has changed since 0.3.0; keys are simpler to use. Let's update everything then add more coverage.
when I use Halite\Asymmetric\Crypto::seal it throw exception crypto_box_seal is not available I had a look into the method and function_exists('\Sodium\crypto_box_seal') return false when it has leading slash but function_exists('Sodium\crypto_box_seal') it return true
i am using Halite 1.0.5
i successfully install php7.2
now going to install libsodium from this link https://paragonie.com/book/pecl-libsodium/read/00-intro.md#installing-libsodium
after installing i tried phpenmod libsodium
and it gives following error
WARNING:
Module libsodium ini file doesn't exist under /etc/php/7.0/mods-available`
now i goto airship cms install and type composer install
`Your requirements could not be resolved to an installable set of packages.
Problem 1
- The requested PHP extension ext-libsodium ^1.0.6 is missing from your system. Install or enable PHP's libsodium extension.
Problem 2
- Installation request for paragonie/halite v2.2.0 -> satisfiable by paragonie/halite[v2.2.0].
- paragonie/halite v2.2.0 requires ext-libsodium ^1.0.6 -> the requested PHP extension libsodium is missing from your system.
To enable extensions, verify that they are enabled in those .ini files:
- /etc/php/7.0/cli/php.ini
- /etc/php/7.0/cli/conf.d/10-opcache.ini
- /etc/php/7.0/cli/conf.d/10-pdo.ini
- /etc/php/7.0/cli/conf.d/15-xml.ini
- /etc/php/7.0/cli/conf.d/20-calendar.ini
- /etc/php/7.0/cli/conf.d/20-ctype.ini
- /etc/php/7.0/cli/conf.d/20-dom.ini
- /etc/php/7.0/cli/conf.d/20-exif.ini
- /etc/php/7.0/cli/conf.d/20-fileinfo.ini
- /etc/php/7.0/cli/conf.d/20-ftp.ini
- /etc/php/7.0/cli/conf.d/20-gd.ini
- /etc/php/7.0/cli/conf.d/20-gettext.ini
- /etc/php/7.0/cli/conf.d/20-iconv.ini
- /etc/php/7.0/cli/conf.d/20-json.ini
- /etc/php/7.0/cli/conf.d/20-mbstring.ini
- /etc/php/7.0/cli/conf.d/20-phar.ini
- /etc/php/7.0/cli/conf.d/20-posix.ini
- /etc/php/7.0/cli/conf.d/20-readline.ini
- /etc/php/7.0/cli/conf.d/20-shmop.ini
- /etc/php/7.0/cli/conf.d/20-simplexml.ini
- /etc/php/7.0/cli/conf.d/20-sockets.ini
- /etc/php/7.0/cli/conf.d/20-sysvmsg.ini
- /etc/php/7.0/cli/conf.d/20-sysvsem.ini
- /etc/php/7.0/cli/conf.d/20-sysvshm.ini
- /etc/php/7.0/cli/conf.d/20-tokenizer.ini
- /etc/php/7.0/cli/conf.d/20-wddx.ini
- /etc/php/7.0/cli/conf.d/20-xmlreader.ini
- /etc/php/7.0/cli/conf.d/20-xmlwriter.ini
- /etc/php/7.0/cli/conf.d/20-xsl.ini
- /etc/php/7.0/cli/conf.d/20-zip.ini
You can also run php --ini
inside terminal to see which files are used by PHP in CLI mode.
`
i tried to install it more that 20 times and evrytime same errror libsodium
any help?
See #23 for a prerequisite.
Someone might decide to reinvent the wheel here. We should consider providing this so someone doesn't shoot themselves in the foot.
Hello,
Because libsodium has made strong efforts to prevent side-channel compromises, do you know if the added protection in this library would prevent or at least inhibit the effectiveness of an exploit on the system using Meltdown or Spectre?
On the surface it would see that the answer to this question is "probably no", however I wonder if the great effort that libsodium has go to, in order to protect memory space; that this library and other libsodium projects are immune or "not as vulnerable" to these types of attacks.
Looking forward to your response,
Thanks!
I noticed this:
./test/phpunit.sh: line 34: [: missing `]'
at
I would asume that when I generate a new hash with Password::hash
that needsRehash
would return false on the generated hash. But as so it seems, it always returns true.
We should add some new methods to the File API:
signFile()
- Ed25519 signature of an unkeyed BLAKE2b hash of the filemacFile()
- keyed BLAKE2b hashverifyFileMac()
verifyFileSignature()
I need to think of better names for the last two.
Also, s/File/Resource/
.
I'm considering whether to flag the API as stable and put this in "maintenance only" mode in Paragon's internal docs, but before I do, what (if anything) does Halite currently not do that you'd like to see it do before it's done?
I am having issues using this library for encrypting / decrypting files.
What is strange is that everything works as expected when running PHP via the cli. Yet running it through nginx (php-fpm) seems to cause issues and the decryption fails consistently with the following error:
Fatal error: Uncaught ParagonIE\Halite\Alerts\InvalidMessage: Invalid message authentication code
I am running running PHP 7 on the laravel homestead environment.
I narrowed it down to the following script:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
@mkdir(__DIR__ . '/enc/');
file_put_contents(__DIR__ . '/enc/test.txt', 'This is a test');
$key = \ParagonIE\Halite\KeyFactory::deriveEncryptionKey('password', str_repeat('a', 16));
@unlink(__DIR__ . '/enc/encrypted');
@unlink(__DIR__ . '/enc/decrypted.txt');
\ParagonIE\Halite\File::encrypt(__DIR__ . '/enc/test.txt', __DIR__ . '/enc/encrypted', $key);
\ParagonIE\Halite\File::decrypt(__DIR__ . '/enc/encrypted', __DIR__ . '/enc/decrypted.txt', $key);
\ParagonIE\Halite\Halite::isLibsodiumSetupCorrectly()
returns true
The following
var_dump([
$major = \Sodium\library_version_major(),
$minor = \Sodium\library_version_minor(),
]);
outputs array(2) { [0]=> int(9) [1]=> int(2) }
And I followed the instructions to compile libsodium from the source from this blog article:
https://paragonie.com/book/pecl-libsodium/read/00-intro.md
Thanks in advance
if [ $? -ne 0 ]; then
echo -e "\033[31mCould not download PGP public key for verification\033[0m"
exit
fi
That should be an exit 1
, else something that only checks the exit code could think the test passed when it was really under attack.
Getting this error with circlical and zf3 on a vagrant machine ubuntu 16.04
vagrant@vagrant:/usr/lib/php/20151012$ pecl search libsodium
Retrieving data...0%
Matched packages, channel pecl.php.net:
=======================================
Package Stable/(Latest) Local
libsodium 1.0.6 (stable) 1.0.6 Wrapper for the Sodium cryptographic library
vagrant@vagrant:/usr/lib/php/20151012$ apt-cache policy libsodium-dev
libsodium-dev:
Installed: 1.0.8-5
Candidate: 1.0.8-5
Version table:
*** 1.0.8-5 500
500 http://us.archive.ubuntu.com/ubuntu xenial/universe amd64 Packages
100 /var/lib/dpkg/status
/var/www/vendor/paragonie/halite/src/KeyFactory.php:51
#0 /var/www/vendor/saeven/zf3-circlical-user/src/CirclicalUser/Service/AuthenticationService.php(542): ParagonIE\Halite\KeyFactory::generateEncryptionKey()
#1 /var/www/module/Application/src/Service/AdminService.php(60): CirclicalUser\Service\AuthenticationService->registerAuthenticationRecord(Object(Application\Entity\User), 'bluebaroncanada...', 'test')
#2 /var/www/module/Application/src/Controller/AdminController.php(46): Application\Service\AdminService->createUser('bluebaroncanada...', 'test', true)
#3 /var/www/vendor/zendframework/zend-mvc/src/Controller/AbstractActionController.php(78): Application\Controller\AdminController->createUserAction()
#4 /var/www/vendor/zendframework/zend-eventmanager/src/EventManager.php(322): Zend\Mvc\Controller\AbstractActionController->onDispatch(Object(Zend\Mvc\MvcEvent))
#5 /var/www/vendor/zendframework/zend-eventmanager/src/EventManager.php(179): Zend\EventManager\EventManager->triggerListeners(Object(Zend\Mvc\MvcEvent), Object(Closure))
#6 /var/www/vendor/zendframework/zend-mvc/src/Controller/AbstractController.php(105): Zend\EventManager\EventManager->triggerEventUntil(Object(Closure), Object(Zend\Mvc\MvcEvent))
#7 /var/www/vendor/zendframework/zend-mvc/src/DispatchListener.php(119): Zend\Mvc\Controller\AbstractController->dispatch(Object(Zend\Http\PhpEnvironment\Request), Object(Zend\Http\PhpEnvironment\Response))
#8 /var/www/vendor/zendframework/zend-eventmanager/src/EventManager.php(322): Zend\Mvc\DispatchListener->onDispatch(Object(Zend\Mvc\MvcEvent))
#9 /var/www/vendor/zendframework/zend-eventmanager/src/EventManager.php(179): Zend\EventManager\EventManager->triggerListeners(Object(Zend\Mvc\MvcEvent), Object(Closure))
#10 /var/www/vendor/zendframework/zend-mvc/src/Application.php(332): Zend\EventManager\EventManager->triggerEventUntil(Object(Closure), Object(Zend\Mvc\MvcEvent))
#11 /var/www/public/index.php(49): Zend\Mvc\Application->run()
#12 {main}
For example:
https://github.com/paragonie/halite/blob/master/src/File.php#L42
Does $inputhandle
get closed when that exception is thrown?
I recently started using Password hashing function which seems very promising in my web app which happens to be using Symfony framework, however it appears that the Symmetric Crypto file is giving a Compile Error as per the debug component (which comes very handy to debug common bugs etc).
This is the exact error given by Symfony debug component.
Compile Error: Cannot use ParagonIE\Halite\Config as Config because the name is already in use
Here is the stack trace that can provide more insight into the problem.
Password::verify($password, $hash, $this->encKey);
$hash_str = Crypto::decrypt($stored, $secretKey);
use ParagonIE\Halite\{ Config,
Solution: It appears that we do not require base Config to be referred in paragonie/halite/src/Symmetric/Crypto.php as Symmetric / Config is the one which should be doing most of the job here anyways. Secondly, splitKey function should use SymmetricConfig as required parameter rather then Config (line 196)
Let me know if above solution makes sense and i can send a quick PR.
Lastly great work by @paragonie-scott - this lib makes crypto such a pain free process.
I see you have a beautiful Sodium stub in this package. It is possible to create a PR here to embed this directly into PHPStorm. Would you be so kind to create the PR? Or, for sure, I can create it for you, if you give me permission to do so.
PHP 7.1.8 (cli) (built: Aug 17 2017 14:29:52) ( NTS
halite 3.2.0
Am I doing something stupid ?
$enc_key = \ParagonIE\Halite\KeyFactory::generateEncryptionKey();
echo strlen($enc_key).PHP_EOL;
Seems to return zero ? Base64 wrapping $enc_key returns nothing ? Have I missed a step ?
In the GitHub rendering, it looks like:
$raw
$encryption_key = Key::generate(Key::CRYPTO_SECRETBOX, $raw);
if (hash_equals($encryption_key->get(), $raw)) {
// This should always return true
}
That $raw
looks like a syntax error to me.
Currently, you have three main Key
derivatives to work with:
\ParagonIE\Halite\Symmetric\SecretKey
\ParagonIE\Halite\Asymmetric\PublicKey
\ParagonIE\Halite\Asymmetric\SecretKey
But each of these can be a key intended for encryption or for authentication/digital signatures, which is determined by a boolean constructor argument.
Proposal: Further split into different types.
\ParagonIE\Halite\Symmetric\SecretKey
\ParagonIE\Halite\Symmetric\AuthenticationKey
\ParagonIE\Halite\Symmetric\EncryptionKey
\ParagonIE\Halite\Asymmetric\PublicKey
\ParagonIE\Halite\Asymmetric\EncryptionPublicKey
\ParagonIE\Halite\Asymmetric\SignPublicKey
\ParagonIE\Halite\Asymmetric\SecretKey
\ParagonIE\Halite\Asymmetric\EncryptionSecretKey
\ParagonIE\Halite\Asymmetric\SignSecretKey
This change will be introduced by adding classes and changing the object type returned by Key::generate()
, etc. The existing classes will not be removed.
This is an idea I came-up with a few days ago after reading https://paragonie.com/blog/2016/09/untangling-forget-me-knot-secure-account-recovery-made-simple
However, even if you apply this mitigation, there's nothing preventing an attacker from just replacing one user's password hash with their own (unique constraints on the database column don't help here; simply change your own password then set the target's to your old password hash, for which you know the correct password).
To prevent someone from re-setting an expired password, would it make sense to include the expiration timestamp with the encrypted password-hash?
With Halite the password is first crypto-hashed and then encrypted with a key.
If you include the expiration timestamp with the encrypted hash: time-stamp:hash
-> encrypt, it would still be possible to re-set the password to a known hash, but after decryption the system will refuse the give an OK as the password is (eventually) expired.
And the attacker shouldn't know the encryption key, and thus is not able to create a correct result.
I'm no security expert, so I would like hear your opinion on this idea ๐
Hello, I do some work to make it compitible with php5.5.9. I don`t know whether it is OK to merge my work into your branch. #52
A number of errors of this nature...
!! PHP Fatal error: Cannot redeclare Sodium\crypto_aead_aes256gcm_is_available() (previously declared in vendor/paragonie/sodium_compat/lib/sodium_compat.php:70) in vendor/paragonie/halite/stub/Sodium.stub.php on line 70
Placeholder ticket. Investigate the usefulness of having:
ParagonIE\Halite\Network\
NonceProvider\
PDOProvider
RandomProvider
ProviderInterface
getNonce()
get a nonce and increase the stored value (for PDO)Crypto
encrypt()
using \Sodium\crypto_aead_encrypt()
after CAESARdecrypt()
using \Sodium\crypto_aead_decrypt()
after CAESARThis might be a useless idea. You should be using TLS.
There's a --batch
switch to gpg
. Not sure if it's worth adding or harmful to add to the commands in phpunit.sh
.
Our PHP application is working correctly with Halite 1.0 on PHP 5.6 and corresponding Libsodium and PECL Libsodium.
Recently, we started migrating our application to Halite v3.2.0, and we are receiving error, as below,
Fatal error: Uncaught Error: Call to undefined function Sodium\hex2bin() in /home/uatpgsw/app/includes/halite-v320/src/KeyFactory.php:676
Stack trace:
#0 /home/uatpgsw/app/includes/halite-v320/src/KeyFactory.php(587): ParagonIE\Halite\KeyFactory::loadKeyFile('/home/uatpgsw/g...')
#1 /home/uatpgsw/public_html/index.php(267): ParagonIE\Halite\KeyFactory::loadEncryptionKeyPair('/home/uatpgsw/g...')
#2 {main}
thrown in /home/uatpgsw/app/includes/halite-v320/src/KeyFactory.php on line 676
Our dev system has:
When I run the following:
<?php var_dump([ SODIUM_LIBRARY_MAJOR_VERSION, SODIUM_LIBRARY_MINOR_VERSION, SODIUM_LIBRARY_VERSION ]); ?>
the response is as follows:
array(3) { [0]=> int(9) [1]=> int(6) [2]=> string(6) "1.0.14" }
Please share, if I am missing something or do we specifically need to use the older versions like PECL Libsodium 1.0.6 / 2.0.4, along with PHP v7.0 and Libsodium v1.0.9, as documented in https://github.com/paragonie/halite?
thank you
Assuming that you don't have build utils, php7 devtools, git, pear&pecl, composer (fresh install)
sudo apt-get install build-essential
sudo apt-get install php7.0-dev
sudo apt-get install php7.1-dev
sudo apt-get install php7.2-dev
sudo apt-get install git
# Clone the libsodium source tree & Build libsodium, perform any defined tests, install libsodium
git clone -b stable https://github.com/jedisct1/libsodium.git && cd libsodium && ./configure && make check && make install
sudo apt-get install pear
pecl install libsodium
(or pecl install -f libsodium-2.0.8
according to comments)libsodium.ini
file (Where <PHP_VERSION> is 7.0 or 7.1 or 7.2)extension=libsodium.so
(or sodium.so
according to comments) in libsodium.ini
& save (Yes, it works like this now, no more php.ini bs)sudo phpenmod libsodium
sudo /etc/init.d/apache2 restart && service php7.0-fpm restart
php -m
sudo apt-get install composer
composer require paragonie/halite
What is the point of the parameters for the functions in KeyFactory
? All of the functions there are like below:
public static function generateAuthenticationKey(string &$secretKey = ''): AuthenticationKey
{
$secretKey = \Sodium\randombytes_buf(
\Sodium\CRYPTO_AUTH_KEYBYTES
);
return new AuthenticationKey(
new HiddenString($secretKey)
);
}
Would that not make the parameters $secretKey
superfluous to pass along? I would refactor the first line like so:
if (is_null($secretKey) || strlen($secretKey) <= \Sodium\CRYPTO_AUTH_KEYBYTES) {
$secretKey = \Sodium\randombytes_buf(
\Sodium\CRYPTO_AUTH_KEYBYTES
);
}
This goes for functions:
============================
Next, this function deriveAuthenticationKey
expects a salt of exactly 16 chars, based on (Util::safeStrlen($salt) !== \Sodium\CRYPTO_PWHASH_SALTBYTES)
comparison. Is this done with a reason? (limitations/design/etc?) Else I wouldn't mind passing along a larger salt, e.g. 32 or 64 chars.
This question goes for functions:
KeyFactory
to accept a "security level" argument for key derivation:
MerkleTree
to accept an output size and personalization string.Key
definitions (thanks @Vinai)Password
If anyone was hoping for anything else to be added to this list, now's the time to make your suggestions/requests.
Either reply here or make a feature request issue. I'll tag appropriately.
It needs to be fixed in halite, not the other way around. The old libsodium.so has been deprecated by the author, and the API has changed, so anything depending on libsodium.so needs to be fixed to use the new API.
What do you think?
Taking simple case from docs. When creating a symmetric encryption key it actually generates a signing key due to missing parenthesis. Operator precedence in PHP evaluates !==
operator before &
operator.
$salt = \Sodium\hex2bin(
'762ce4cabd543065172236de1027536ad52ec4c9133ced3766ff319f10301888'
);
$enc_secret = Key::deriveFromPassword(
'correct horse battery staple',
$salt,
Key::ENCRYPTION | Key::SECRET_KEY
);
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.